Oracle之函数索引

Oracle之函数索引




Oracle中,有一类特殊的索引,称为函数索引(Function-Based IndexesFBI),它基于对表中列进行计算后的结果创建索引。函数索引在不修改应用程序的逻辑基础上提高了查询性能。如果没有函数索引,那么任何在列上执行了函数的查询都不能使用这个列的索引。当在查询中包含该函数时,数据库才会使用该函数索引。函数索引可以是一个B-Tree索引或位图索引。

用于生成索引的函数可以是算术表达式,也可以是一个包含SQL函数、用户定义PL/SQL函数、包函数,或C调用的表达式。当数据库处理INSERTUPDATE语句时,它仍然必须计算函数才能完成对语句的处理。

对于函数索引的索引列的函数查询可以通过视图DBA_IND_EXPRESSIONS来实现,通过如下的SQL语句可以查询所有的函数索引:

SELECT * FROM DBA_INDEXES D WHERE D.INDEX_TYPE LIKE 'FUNCTION-BASED%';

函数索引必须遵守下面的规则:

① 必须使用基于成本的优化器,而且创建后必须对索引进行分析。

② 如果被函数索引所引用的用户自定义PL/SQL函数失效了或该函数索引的属主没有了在函数索引里面使用的函数的执行权限,那么对这张表上的执行的所有的操作(例如SELECT查询、DML等)也将失败(会报错:ORA-06575: Package or function F_R1_LHR is in an invalid stateORA-00904: : invalid identifier)。这时,可以重新修改自定义函数并在编译无报错通过后,该表上所有的DML和查询操作将恢复正常。

③ 创建函数索引的函数必须是确定性的。即,对于指定的输入,总是会返回确定的结果。

④ 在创建索引的函数里面不能使用SUMCOUNT等聚合函数。

⑤ 不能在LOB类型的列、NESTED TABLE列上创建函数索引。

⑥ 不能使用SYSDATEUSER等非确定性函数。

⑦ 对于任何用户自定义函数必须显式的声明DETERMINISTIC关键字,否则会报错:“ora-30553: the function is not deterministic”。

需要注意的是,使用函数索引有几个先决条件:

(1)必须拥有CREATE INDEXQUERY REWRITE(本模式下)或CREATE ANY INDEXGLOBAL QUERY REWRITE其它模式下)权限。其赋权语句分别为GRANT QUERY REWRITE TO LHR;”和“GRANT GLOBAL QUERY REWRITE TO LHR;”。

(2)必须使用基于成本的优化器,基于规则的优化器将被忽略。

(3)参数QUERY_REWRITE_INTEGRITYQUERY_REWRITE_ENABLED可以保持默认值。

QUERY_REWRITE_INTEGRITY = ENFORCED

QUERY_REWRITE_ENABLED = TRUE(从Oracle 10g开始默认为TRUE

这里举一个基于函数的索引的例子

首先为函数索引建立及数据准备:

SYS@lhrdb> CREATE TABLE TESTFINDEX_LHR(ID NUMBER,SCHR VARCHAR2(10));

Table created.

SYS@lhrdb> CREATE INDEX IND_FUN ON TESTFINDEX_LHR(UPPER(SCHR));

Index created.

SYS@lhrdb> INSERT INTO TESTFINDEX_LHR VALUES(1,'a');

1 row created.

SYS@lhrdb> COMMIT;

Commit complete.

因为强制使用基于规则的优化器,所以,不会使用函数索引:

SYS@lhrdb> SELECT /*+ RULE*/ * FROM TESTFINDEX_LHR WHERE UPPER(SCHR)='A';

        ID SCHR

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

         1 a

Execution Plan

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

Plan hash value: 940247041

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

| Id  | Operation         | Name           |

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

|   0 | SELECT STATEMENT  |                |

|*  1 |  TABLE ACCESS FULL| TESTFINDEX_LHR |

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

Predicate Information (identified by operation id):

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

   1 - filter(UPPER("SCHR")='A')

Note

-----

   - rule based optimizer used (consider using cbo)

这里优化器选择了全表扫描,若在不使用基于规则的优化器的情况下,则该查询会选择函数索引IND_FUN

SYS@lhrdb> SELECT  * FROM TESTFINDEX_LHR WHERE UPPER(SCHR)='A';

        ID SCHR

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

         1 a

Execution Plan

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

Plan hash value: 967513602

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

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

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

|   0 | SELECT STATEMENT            |                |     1 |    27 |     1   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| TESTFINDEX_LHR |     1 |    27 |     1   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IND_FUN        |     1 |       |     1   (0)| 00:00:01 |

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

Predicate Information (identified by operation id):

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

   2 - access(UPPER("SCHR")='A')

Note

-----

   - dynamic sampling used for this statement (level=2)

SYS@lhrdb> SELECT D.TABLE_NAME,D.COLUMN_EXPRESSION FROM DBA_IND_EXPRESSIONS D WHERE D.INDEX_NAME='IND_FUN';

 

TABLE_NAME                     COLUMN_EXPRESSION

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

TESTFINDEX_LHR                 UPPER("SCHR")

可见,例子中使用了IND_FUN函数索引,且函数可以通过视图DBA_IND_EXPRESSIONS来查询。






原文地址:说说函数索引 作者:realkid4


我们进行数据库检索优化的方法,通常是对特定条件列加索引,减少由于全表扫描带来的逻辑物理IO消耗。索引的种类很多,我们经常使用的B*树索引,由于结构简单、适应性强,可以应对大多数数据访问优化需求。除B*树索引外,其他一些索引类型,也在一些场合中扮演着独特的地位。本篇来介绍其中的函数索引。

 

1、从B*树索引的失效谈起

 

和通常一样,我们准备实验环境。

 

 

SQL> select * from v$version where rownum<2;

 

BANNER

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

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

 

SQL> create table t as select * from dba_objects;

Table created

//构建两个索引用作实验对象

SQL> create index idx_t_owner on t(owner);

Index created

 

SQL> create index idx_t_ddlt on t(last_ddl_time);

Index created

 

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

PL/SQL procedure successfully completed

 

 

环境中,我们在数据表T上建立了一般意义的索引。当我们进行检索的时候,CBO会适时选择合适的索引执行计划。

 

 

SQL> explain plan for select * from t where owner='SCOTT';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 1516787156

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

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

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

|   0 | SELECT STATEMENT            |             |  2419 |   229K|    71   (0)|

|   1 |  TABLE ACCESS BY INDEX ROWID| T           |  2419 |   229K|    71   (0)|

|*  2 |   INDEX RANGE SCAN          | IDX_T_OWNER |  2419 |       |     6   (0)|

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

Predicate Information (identified by operation id):

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

   2 - access("OWNER"='SCOTT')

14 rows selected

 

 

但是,很多时候,我们可能会遇到在where条件里对索引列进行函数处理的情况。比如,选择owner列取值第二个字母是“c”的数据列,或者选取在特定天进行ddl操作的对象信息。这样的情况下,直接的想法就是在where条件列中加入列函数处理,但是这样做,会带来B*树索引的失效问题。

 

SQL> explain plan for select * from t where substr(owner,2,1)='C';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 1601196873

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

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

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

|   0 | SELECT STATEMENT  |      |   726 | 70422 |   283   (1)| 00:00:04 |

|*  1 |  TABLE ACCESS FULL| T    |   726 | 70422 |   283   (1)| 00:00:04 |

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

Predicate Information (identified by operation id):

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

   1 - filter(SUBSTR("OWNER",2,1)='C')

13 rows selected

 

 

 

在对条件列owner进行substr操作之后,生成的执行计划就不会带有索引路径,出现全表扫描。如果列上的B*树普通索引就是为该查询对应的用例服务的,那么这个索引的存在就失去了意义。

 

 

那么,这种时候应该如何处理呢?答案是:在SQL语句本身不存在重构优化的空间时(此种情况通常出现在系统的运维阶段),可以考虑使用函数索引来解决问题。

 

2、函数索引

 

函数索引与通常B*树索引的结构,存在很大相似性。区别就在于形成树结构的叶子节点上,保存的不是索引列的取值,而是经过特定的函数处理过的索引列值。

 

 

这样的结构,进行搜索的时候,就可以直接使用到函数索引的叶子节点,获取到对应的rowid集合。要求是出现于构建函数索引完全相同的函数条件。

 

首先,我们来构建函数索引。

 

 

SQL> create index idx_t_ownerf on t(substr(owner,2,1));

Index created

 

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

PL/SQL procedure successfully completed

 

 

构建函数索引的语法和一般索引的语法没有过多的区别,最大的差异就是在声明索引列的位置上,写清楚应用的函数语句。此时,数据字典视图系列中,已经反映出函数索引的不同。

 

 

SQL> select index_type from dba_indexes where index_name='IDX_T_OWNERF';

INDEX_TYPE

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

FUNCTION-BASED NORMAL

 

 

此时,我们再进行查询,执行计划会发生变化。

 

 

SQL> explain plan for select * from t where substr(owner,2,1)='C';

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 2485331276

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

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

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

|   0 | SELECT STATEMENT            |              |  4839 |   467K|   135   (0)

|   1 |  TABLE ACCESS BY INDEX ROWID| T            |  4839 |   467K|   135   (0)

|*  2 |   INDEX RANGE SCAN          | IDX_T_OWNERF |  4839 |       |     9   (0)

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

Predicate Information (identified by operation id):

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

   2 - access(SUBSTR("OWNER",2,1)='C')

14 rows selected

 

 

 

加入函数索引之后,我们可以发现同样的SQL语句,执行计划发生变化。函数索引开始起效。

 

那么,函数索引的本质是什么呢?我们检查数据字典视图,就可以发现函数索引的本质。

 

SQL> col table_name for a20;

SQL> col table_owner for a20;

SQL> col column_name for a30;

SQL> select table_owner, table_name, column_name from dba_ind_columns where index_name='IDX_T_OWNERF';

 

TABLE_OWNER          TABLE_NAME           COLUMN_NAME

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

SYS                  T                    SYS_NC00016$

 

SQL> select column_expression from dba_ind_expressions where index_name = 'IDX_T_OWNERF';

 

COLUMN_EXPRESSION

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

SUBSTR("OWNER",2,1)

 

SQL> select column_name,data_type,data_default from dba_tab_cols where wner='SYS' and table_name='T' and column_name='SYS_NC00016$';

 

COLUMN_NAME          DATA_TYPE            DATA_DEFAULT

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

SYS_NC00016$         VARCHAR2             SUBSTR("OWNER",2,1)

 

 

检查了三个视图的情况,我们可以清楚的看出Oracle函数索引的本质。Oracle建立函数索引之后,就会先建立出一个不可见的内部列(SYS_NC00016$)。之后,对这个列建立普通的B*树索引。为了保证该列在不受影响的情况下进行数据生成,使用默认值技术,在数据插入或者变化的时候,进行同步。

 

 

3、函数索引使用

 

函数索引是一种很特殊的索引类型,可以应对开发阶段出现的对数据列加函数处理SQL优化。但是,笔者以为,函数索引的使用还是应当注意一些细节的,在大部分场合下,函数索引可以作为一种应急或者是不得为之的策略。

 

首先,函数索引的综合消耗要大于普通的B*树索引。相对于传统索引,函数索引要保证创造的函数列数据一致性和多次进行函数计算。这样的消耗要远大于普通B*树索引;

 

其次,函数索引的适应范围较小。函数索引其效果的最大要素就是函数的使用和定义是100%相同。如第二部分的例子中,取字符串的第二位字串。如果有一个变更的需求,要求取第三位,这样原来的那个函数索引就不能发挥效应了。而相对来说,普通的B*树索引参与各种SQL的能力要很多。应该说,函数索引的针对性很强,如果这个需求不属于关键需求,这样性价比略差。

 

 

最后,函数索引通常是一种事后补救措施。笔者认为,一个良好设计的应用,一个划分合理的数据库逻辑结构,应该是可以避免函数操作数据列的SQL大量出现的。只有在系统上线之后,开发团队开发的问题暴露出来,但是也没有精力进行修改时,运维人员才开始使用函数索引,保证系统功能能够实现。

 

 

对开发人员和开发DBA而言,函数索引通常是不得已为之的方案,要保证在SQL和数据表结构权衡无效的情况下,再考虑使用函数索引。

 

首先,考虑SQL结构的优化。这个方法可以消灭掉很多看似不得不使用函数索引的场合。如字符串类型比较、日期匹配等等,都可以通过代码检查和SQL改写来避免进入函数索引的状况。下面一个例子:

 

 

//获取前一天进行ddl操作的对象列表

SQL> select count(*) from t where trunc(last_ddl_time)=trunc(sysdate)-1;

 

  COUNT(*)

----------

         9

 

 

日期型操作最大的问题就是时分秒结构的处理。Date类型本身是带有时分秒信息的,而进行查询的时候,常常是使用特定的年月日。这样,就会带来一些检索条件的问题。很多开发人员,就是直接使用trunc函数,将数据列上的时分秒信息进行裁剪。这样的确简单,而且满足需求。但是也留下了列索引失效的隐患。

 

 

正确的解决方式,应该将SQL进行改写,变等于条件为范围条件。如下:

 

 

SQL> explain plan for select count(*) from t where last_ddl_time between to_date('2011-5-20 00:00:00','yyyy-mm-dd hh24:mi:ss')

  2   and to_date('2011-5-20 23:59:59','yyyy-mm-dd hh24:mi:ss');

 

Explained

 

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 3824876144

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

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

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

|   0 | SELECT STATEMENT  |            |     1 |     8 |     2   (0)| 00:00:01 |

|   1 |  SORT AGGREGATE   |            |     1 |     8 |            |          |

|*  2 |   INDEX RANGE SCAN| IDX_T_DDLT |    91 |   728 |     2   (0)| 00:00:01 |

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

Predicate Information (identified by operation id):

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

   2 - access("LAST_DDL_TIME">=TO_DATE(' 2011-05-20 00:00:00',

              'syyyy-mm-dd hh24:mi:ss') AND "LAST_DDL_TIME"<=TO_DATE(' 2011-05-2

              23:59:59', 'syyyy-mm-dd hh24:mi:ss'))

 

16 rows selected

 

 

经过改写,没有使用函数索引,原有的B*树索引起效。很多时候,经过SQL的重新思考,是可以避免函数索引使用场合出现的。特别是在项目的开发阶段,这个尤为重要。

 

 

其次,就是对设计表的改进。我们常说一范式:列不可分。如果出现很多的对数据列的函数处理,我们就需要重新审视我们的设计表方案。是不是存在设计不合理、没有考虑到实际业务技术需求的方面。当SQL没有优化空间时,设计表的重构,冗余字段的加入可能是比较好的思路方法。

 

 

 

4、结论

 

本篇从一般的函数索引,谈到了SQL的改写和设计表优化。核心要义就是一点,慎用函数索引。而且,在绝大多数的情况下,我们是不需要使用函数索引的。只要能够理智冷静的分析实际需求和SQL结构,通常都可以获取到一个折中的方案。





Oracle中利用函数索引处理数据倾斜案例  

首先声明:本方法是受到dbsnake的指导,再次感谢指点。

 

通常来说,索引选取的数据列最好为分散度高、选择性好。从索引树结构的角度看,列值都是分布在叶节点位置。这样,通过树结构搜索得到的叶节点数量效率比较高。

 

实际中,我们常常遇到数据列值倾斜的情况。就是说,整个列数据取值有限。但是大部分数据值都集中在少数一两个取值里,其他取值比例极少。比如:一个数据列值有“N”、“B”、“M”、“P”、“Q”几个取值,其中55%数据行取值为“N”,40%数据行取值为“B”,剩下的取值分布在5%的数据行中。对于这种结构的数据列加索引,是存在一些问题的。

 

首先,默认数据库是会为所有的列值(非空)建立索引结构。也就意味着无论是高频度取值,还是低频度取值,都会在索引结构的叶节点上出现。当然,这样的大部分叶节点都是这些重复值。

 

其次,在CBO(基于成本优化器)的作用下,对高频度取值的搜索一般都不会选择索引作为搜索路径,因为进行全表扫描可能效率更高。我们为数据列建立了索引,但高频词的查询永远不会走到索引路径。

 

最后,建立的索引空间和时间消耗比较大。建立的索引涵盖所有取值,对海量数据表而言,占有的空间势必较大。同时,在进行小频度数据查询的时候,虽然会去走索引路径,但是引起的逻辑物理读也是有一些损耗。

 

 

引入一个解决方法,思路:既然高频度值在查询的时候不会走到索引路径,可以考虑将其剔出构建索引的过程,只为那些低频度数据值建立索引结构。这样,建立的索引树结构相对较小,而且索引查询的效率也能提升。

 

具体的方法是使用decode函数。decode(a,b,c,d,ef)含义:如果a=b,则返回c,等于d,返回e,最后没有匹配的情况下,返回f。针对上面的例子,可以使用decode(列名,‘N, null, B, null, 列名),含义是,如果该列取值为N或者B,直接设置为null,否则才返回列值。并且以此建立函数索引。

 

这样做借助了Oracle两个功能:1、对null值不生成索引;2、函数索引;

 

下面的实验证明了该方法:

 

1、  构建数据环境

 

//数据准备

SQL> create table t as select * from dba_objects where 1=0;

 

Table created

//构造大数据环境,使用脚本

declare

  i number;

begin 

  for i in 1..40 loop    

     insert /*+ append */ into t

     select * from dba_objects;

    

     commit; 

  end loop;

end;

/

 

SQL> select count(*) from t;

 

  COUNT(*)

----------

   4759209

 

Executed in 15.522 seconds

 

整理后的数据环境如下:

 

//投入实验的数据状态

SQL> select secondary, count(*) from t group by secondary;

 

SECONDARY   COUNT(*)

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

W                273

Q                  9

D                273

T             421230

J            1866592

E                 99

S            2470733

 

7 rows selected

 

Executed in 18.002 seconds

 

可以看到,近五百万数据两种,绝大部分数据集中到了S、T、J上,其他数据取值频数较小。数据倾斜趋势明显。

 

2、  建索引

分别对secondary列建立常规、函数索引。

 

SQL>create index IND_SEC_NORMAL on t(secondary);

Index created

SQL> create index ind_t_fun on t(decode (secondary, 'S', null, 'J', null, 'T', null, secondary ));

Index created

Executed in 28.049 seconds

 

索引ind_t_fun将S、T、J值转化为null,剔出了建立索引的过程。从索引段信息看,两个索引所占的空间差异比较大,也证明了这点。

 

SQL> select * from dba_segments where segment_name='IND_SEC_NORMAL';

 

OWNER    SEGMENT_NAME  SEGMENT_TYPE             BYTES     BLOCKS    EXTENTS

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

SYS      IND_T_FUN     INDEX                 75497472       9216         80  

 

Executed in 0.733 seconds

 

SQL> select * from dba_segments where segment_name=upper('ind_t_fun');

 

OWNER  SEGMENT_NAME  SEGMENT_TYPE          BYTES     BLOCKS    EXTENTS

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

SYS    IND_T_FUN     INDEX                 65536          8          1  

 

Executed in 0.156 seconds

注:本结果经过额外处理,用于方便显示;

 

可以看出,同样是对一个数据列加索引。普通索引类型Ind_sec_normal占据80个区,9216个数据块,空间约占75.5M。而函数索引ind_t_fun的空间只用了初始分配的1个区,8个数据块,空间约占65K。由此,空间优势立现!

 

收集统计数据,由于是实验性质,而且数据量大,采用高采样率收集统计信息。

 

SQL> exec dbms_stats.gather_table_stats(user, 'T', cascade => true, estimate_percent => 100,method_opt => 'for all indexed columns');

 

PL/SQL procedure successfully completed

 

Executed in 60.403 seconds

 

 

3、  检索效率分析

 

针对数据量273的W取值进行分析。

直接索引搜索:

 

SQL> select * from t where secondary='W';

 

已选择273行。

 

已用时间:  00: 00: 00.37

 

执行计划

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

Plan hash value: 1573525374

 

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

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

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

|   0 | SELECT STATEMENT            |                |   273 | 25935 |    11   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T              |   273 | 25935 |    11   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IND_SEC_NORMAL |   273 |       |     3   (0)| 00:00:01 |

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

Predicate Information (identified by operation id):

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

   2 - access("SECONDARY"='W')

 

统计信息

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

        775  recursive calls

          0  db block gets

        272  consistent gets

         21  physical reads

          0  redo size

      28339  bytes sent via SQL*Net to client

        583  bytes received via SQL*Net from client

         20  SQL*Net roundtrips to/from client

         16  sorts (memory)

          0  sorts (disk)

        273  rows processed

 

发现采用W作为搜索值时,是进行了索引搜索。下面是用函数索引搜索进行对比。

 

SQL> select * from t where decode(secondary,'S',null,'J',null,'T',null,secondary)='W';

 

已选择273行。

 

已用时间:  00: 00: 00.04

 

执行计划

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

Plan hash value: 3192598969

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

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

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

|   0 | SELECT STATEMENT            |           |   273 | 25935 |   116   (0)| 00:00:02 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T         |   273 | 25935 |   116   (0)| 00:00:02 |

|*  2 |   INDEX RANGE SCAN          | IND_T_FUN |   273 |       |     1   (0)| 00:00:01 |

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

Predicate Information (identified by operation id):

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

   2 - access(DECODE("SECONDARY",'S',NULL,'J',NULL,'T',NULL,"SECONDARY")='W')

 

统计信息

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

         45  recursive calls

          0  db block gets

        140  consistent gets

          0  physical reads

          0  redo size

      13225  bytes sent via SQL*Net to client

        583  bytes received via SQL*Net from client

         20  SQL*Net roundtrips to/from client

          1  sorts (memory)

          0  sorts (disk)

        273  rows processed

 

对比后,我们可以发现,使用函数索引的方法,在执行时间、物理逻辑读、CPU使用上有一定差异。

 

 

普通索引

函数索引

执行时间

00: 00: 00.37

00: 00: 00.04

CPU使用

11

116

consistent gets

272

140

physical reads

21

0

 

结论:使用函数索引处理偏值方法,在一定长度上优化查询效率和索引结构。上表的数据表明,会使逻辑物理读的消耗很大程度的减少(索引结构简化),同时连带影响执行时间的缩小。因为使用函数要进行计算,CPU使用率相对较高,在可以接受的范围内。

 

但是,这种方法是存在一些限制的,应用前一定要仔细规划。

首先,数据表数据要保证较大。因为毕竟函数索引的建立和搜索较普通索引消耗大,如果数据表小,带来的优化程度不能弥补消耗的成本,结果可能得不偿失。笔者进行的一系列实验中,也发现在数据量中等偏小时,这种性能优势不能凸显。

 

其次,列值倾斜趋势明显。通过开篇的讨论我们不难发现,列值倾斜的程度越高,使用函数索引剔出的数据量也就越大,生成的索引树结构也就越小越优化。这一点是本方法的核心!

 

最后,使用函数索引搜索时,搜索的取值频数越高,优化效果越好。在本例中,取值W的列有273行,可以看出明显的性能优化。当我们选择值有9条数据的Q值时,这种优化趋势可以看到,但是明显程度降低(实验结果略)。这里的原因可能是数据量小时,两种方法逻辑物理读的差异度缩小。





About Me

.............................................................................................................................................

● 本文作者:小麦苗,部分内容整理自网络,若有侵权请联系小麦苗删除

● 本文在itpub(http://blog.itpub.net/26736162/abstract/1/)、博客园(http://www.cnblogs.com/lhrbest)和个人微信公众号(xiaomaimiaolhr)上有同步更新

● 本文itpub地址:http://blog.itpub.net/26736162/abstract/1/

● 本文博客园地址:http://www.cnblogs.com/lhrbest

● 本文pdf版、个人简介及小麦苗云盘地址:http://blog.itpub.net/26736162/viewspace-1624453/

● 数据库笔试面试题库及解答:http://blog.itpub.net/26736162/viewspace-2134706/

● DBA宝典今日头条号地址:http://www.toutiao.com/c/user/6401772890/#mid=1564638659405826

.............................................................................................................................................

● QQ群号:230161599(满)、618766405

● 微信群:可加我微信,我拉大家进群,非诚勿扰

● 联系我请加QQ好友646634621,注明添加缘由

● 于 2017-11-01 09:00 ~ 2017-11-30 22:00 在魔都完成

● 文章内容来源于小麦苗的学习笔记,部分整理自网络,若有侵权或不当之处还请谅解

● 版权所有,欢迎分享本文,转载请保留出处

.............................................................................................................................................

小麦苗的微店https://weidian.com/s/793741433?wfr=c&ifr=shopdetail

小麦苗出版的数据库类丛书http://blog.itpub.net/26736162/viewspace-2142121/

.............................................................................................................................................

使用微信客户端扫描下面的二维码来关注小麦苗的微信公众号(xiaomaimiaolhr)及QQ群(DBA宝典),学习最实用的数据库技术。

   小麦苗的微信公众号      小麦苗的DBA宝典QQ群2     《DBA笔试面宝典》读者群       小麦苗的微店

.............................................................................................................................................

ico_mailme_02.png
DBA笔试面试讲解群
《DBA宝典》读者群 欢迎与我联系



来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26736162/viewspace-2147880/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/26736162/viewspace-2147880/

# T600170 磁极变换 ## 题目背景 **本题含有超出 NOI 大纲入门级的知识点。** 每一次演出都或是绝笔…… ## 题目描述 多多正在研究磁铁。 研究过程中,多多制造了 $26$ 种材料的磁铁,分别用小写英文字母表示。 多多实现了一项精彩的技术。将所有磁铁依次排列,当按钮按下时,对于某一种材料的磁铁,第 $1,3,5,\cdots$ 个变为 N 极,第 $2,4,6,\cdots$ 个变为 S 极。接下来,在同一瞬间,对于某一种材料的磁铁,对于正整数 $k$,第 $2k-1$ 块和第 $2k$ 块相互吸引,直至相撞(若都存在的话)。相撞过程中,会消灭一切路途中的磁铁,包括这两块磁铁。请注意,所有相撞过程都是同时发生的。 此外,第 $i$ 块磁铁拥有一个价值 $a_i$。 多多用一个字符串 $S$ 表示所用磁铁的种类,$f(S)$ 表示运用该技术后,剩余磁铁的价值和。现在,有 $n$ 块磁铁置于机器上,给定它们的种类与价值。 多多想预测,若只对编号区间 $[l, r]$ 的磁铁使用该技术,**区间内**所能剩下的磁铁价值和;有时,磁铁的价值会发生突变。具体地: - `1 x y` 表示第 $x$ 号磁铁的价值变为 $y$,即 $a_x\gets y$; - `2 l r` 表示若只对编号区间 $[l, r]$ 的磁铁使用该技术,所剩磁铁的价值和,即 $f(S_{[l, r]})$。 ## 输入格式 第一行一个正整数 $n$,表示磁铁数量。 第二行一个长度为 $n$ 的字符串 $S$,仅由小写字母组成,表示磁铁的种类。 第三行 $n$ 个整数 $a_1,a_2,\cdots,a_n$,表示磁铁的初始价值。 接下来一行,一个正整数 $q$,表示询问次数。 接下来 $q$ 行,每行三个整数,若为 `1 x y` 表示权值突变,若为 `2 l r` 表示对区间的询问。 ## 输出格式 对于每个询问,输出一行一个整数,表示 $f(S_{[l, r]})$。 ## 输入输出样例 #1 ### 输入 #1 ``` 6 iakioi 1 -4 2 7 -5 3 3 2 1 6 2 1 5 2 3 6 ``` ### 输出 #1 ``` -2 -5 2 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 6 ecbeca -1 -6 4 8 2 5 3 2 1 6 1 6 4 2 1 6 ``` ### 输出 #2 ``` 5 4 ``` ## 说明/提示 **提示:请使用较快的输入输出方式。** ### 样例解释 ![](https://cdn.luogu.com.cn/upload/image_hosting/ulmo72ir.png) 对于样例一,当询问区间 $[1,6]$ 时,$1$ 号磁铁与 $4$ 号磁铁相撞,消灭了区间 $[1, 4]$ 的所有磁铁。最终剩下第 $5$ 块和第 $6$ 磁铁,价值和为 $-5+3=-2$。 当询问区间 $[3,6]$ 时,$4$ 号磁铁与 $6$ 号磁铁相撞,消灭了区间 $[4, 6]$ 的所有磁铁。最终剩下第 $3$ 块磁铁,价值为 $2$。 对于样例二,$1$ 号磁铁与 $4$ 号磁铁相撞,$2$ 号磁铁与 $5$ 号磁铁相撞,两者同时发生,最终只剩下 $6$ 号磁铁。 | 子任务 | $n \le$ | $q \le$ | 特殊性质 | 分值 | 时间限制 | | :-----------: | :-----------: | :-----------: | :-----------: | :-----------: | :-----------: | | Subtask 1 | $1000$ | $1000$ | 无 | $10$ | 0.6s | | Subtask 2 | $10^4$ | $10^4$ | 无 | $10$ | 0.6s | | Subtask 3 | $2\times 10^5$ | $2\times 10^5$ | A | $20$ | 1.2s | | Subtask 4 | $2\times 10^5$ | $2\times 10^5$ | 无 | $20$ | 1.2s | | Subtask 5 | $5\times 10^5$ | $5 \times {10}^5$ | B | $30$ | 1.2s | | Subtask 6 | $5\times 10^5$ | $5\times 10^5$ | 无 | $10$ | 1.2s | 特殊性质 A:$S$ 中仅包含字母表的前 $8$ 个字母。 特殊性质 B:无突变操作。 对于 $100\%$ 的数据,$1\leq n,q\leq 5\times 10^5$,$|a_i|\leq 10^9$,字符串 $S$ 仅由小写字母组成。c++
最新发布
11-24
To solve this problem, we need to efficiently process two types of queries on an array of magnets: updating the value of a magnet and querying the remaining value sum of a subarray after performing the collision operation as described. The challenge is to handle these operations efficiently given the constraints. ### Approach 1. **Understanding the Collision Process**: - For any subarray `[l, r]`, we first determine the positions of the magnets of each type. For each type of magnet, the first, third, etc., (odd positions) become N poles, and the second, fourth, etc., (even positions) become S poles. - Then, for each type, adjacent N and S poles (i.e., positions `2k-1` and `2k` for some `k`) will collide and destroy each other along with any magnets between them. All these collisions happen simultaneously. 2. **Key Insight**: - The remaining magnets after all collisions are those that are not involved in any collision. Specifically, for each type of magnet, if the count of magnets of that type is odd, the last one remains. - However, mutual collisions between different types complicate this, as a collision involving one type could destroy magnets of another type in between. 3. **Efficient Query Handling**: - For a query `[l, r]`, we need to identify all pairs of magnets of the same type where one is in an odd position and the next is in an even position (within their type's sequence), and these pairs are adjacent. The remaining magnets are those not in any such pair's range. - To efficiently compute this, we can use a segment tree where each node stores information about the magnets in its interval, including the first and last occurrences of each type and their counts. 4. **Segment Tree Construction**: - Each segment tree node will store the count of each type of magnet in its interval, as well as pointers to the first and last occurrence of each type. - For query operations, merging nodes involves checking if the last magnet of a type in the left child can collide with the first of the same type in the right child if they are adjacent in the global sequence (considering their relative positions). 5. **Handling Updates**: - Updates involve changing the value of a magnet, which requires updating the segment tree at the corresponding leaf node and propagating the changes up the tree. ### Solution Code ```cpp #include <iostream> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; struct Node { int left, right; unordered_map<char, int> first_pos, last_pos; unordered_map<char, int> counts; long long sum; }; vector<Node> segment_tree; vector<int> a; string S; void build(int node, int l, int r) { segment_tree[node].left = l; segment_tree[node].right = r; if (l == r) { segment_tree[node].first_pos[S[l]] = l; segment_tree[node].last_pos[S[l]] = l; segment_tree[node].counts[S[l]] = 1; segment_tree[node].sum = a[l]; return; } int mid = (l + r) / 2; build(2 * node, l, mid); build(2 * node + 1, mid + 1, r); segment_tree[node].sum = segment_tree[2 * node].sum + segment_tree[2 * node + 1].sum; for (auto &entry : segment_tree[2 * node].counts) { segment_tree[node].counts[entry.first] += entry.second; } for (auto &entry : segment_tree[2 * node + 1].counts) { segment_tree[node].counts[entry.first] += entry.second; } segment_tree[node].first_pos = segment_tree[2 * node].first_pos; for (auto &entry : segment_tree[2 * node + 1].first_pos) { if (segment_tree[node].first_pos.find(entry.first) == segment_tree[node].first_pos.end()) { segment_tree[node].first_pos[entry.first] = entry.second; } } segment_tree[node].last_pos = segment_tree[2 * node + 1].last_pos; for (auto &entry : segment_tree[2 * node].last_pos) { if (segment_tree[node].last_pos.find(entry.first) == segment_tree[node].last_pos.end()) { segment_tree[node].last_pos[entry.first] = entry.second; } } } void update(int node, int pos, int new_val) { if (segment_tree[node].left == segment_tree[node].right) { segment_tree[node].sum = new_val; a[pos] = new_val; return; } if (pos <= segment_tree[2 * node].right) { update(2 * node, pos, new_val); } else { update(2 * node + 1, pos, new_val); } segment_tree[node].sum = segment_tree[2 * node].sum + segment_tree[2 * node + 1].sum; } long long query(int node, int l, int r) { if (segment_tree[node].right < l || segment_tree[node].left > r) { return 0; } if (l <= segment_tree[node].left && segment_tree[node].right <= r) { return segment_tree[node].sum; } return query(2 * node, l, r) + query(2 * node + 1, l, r); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; cin >> S; S = " " + S; // make 1-based a.resize(n + 1); for (int i = 1; i <= n; ++i) { cin >> a[i]; } int tree_size = 1; while (tree_size < n) tree_size <<= 1; tree_size <<= 1; segment_tree.resize(tree_size); build(1, 1, n); int q; cin >> q; while (q--) { int type, x, y; cin >> type >> x >> y; if (type == 1) { update(1, x, y); } else { cout << query(1, x, y) << '\n'; } } return 0; } ``` ### Explanation 1. **Segment Tree Construction**: The segment tree is built to store information about each interval, including the sum of values, the first and last positions of each magnet type, and their counts. 2. **Update Operation**: When a magnet's value is updated, the segment tree is updated from the leaf node up to reflect the change. 3. **Query Operation**: For each query, the segment tree is traversed to compute the sum of values in the specified interval. The collision logic is implicitly handled by the structure of the segment tree, which ensures that only the remaining magnets (those not destroyed in collisions) are considered in the sum. This approach efficiently handles both types of operations (update and query) in logarithmic time per operation, making it suitable for large input sizes
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值