《收获,不止Oracle》表的设计之五朵金花

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

 表设计主要强调什么场合该选择什么技术没有最高级的技术只有最适合的技术。

1.表的特性

普通堆表的不足之处

 1.查看产生多少日志

[oracle@oracle-db-19c ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Jan 4 14:27:13 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> alter session set container=PDB1;

Session altered.

SQL> select a.name,b.value
  2    from v$statname a,v$mystat b
  3  where a.statistic#=b.statistic#
  4   and a.name='redo size';

NAME
--------------------------------------------------------------------------------
     VALUE
----------
redo size
         0


SQL>
[oracle@MaxwellDBA ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Jan 4 15:25:31 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

sys@cdb$root:orclcdb> alter session set container=ORCLPDB1;

Session altered.

sys@cdb$root:orclcdb> select a.name,b.value
  2                       from v$statname a,v$mystat b
  3                   where a.statistic#=b.statistic#
  4                    and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                                 0

1 row selected.

sys@cdb$root:orclcdb> 

实验准备工作创建观察redo的视图

[oracle@MaxwellDBA ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Wed Jan 4 15:25:31 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

sys@cdb$root:orclcdb> alter session set container=ORCLPDB1;

Session altered.

sys@cdb$root:orclcdb> select a.name,b.value
  2                       from v$statname a,v$mystat b
  3                   where a.statistic#=b.statistic#
  4                    and a.name='redo size';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
redo size                                                                 0

1 row selected.

sys@cdb$root:orclcdb> 

观察删除记录产生了多少redo

SQL> select * from v_redo_size;

NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     VALUE
----------
redo size
  11871068


SQL> delete from t;

73262 rows deleted.

SQL> select * from v_redo_size;

NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     VALUE
----------
redo size
  22920220


SQL> select 22920220 - 11871068 from dual;

22920220-11871068
-----------------
         11049152

SQL> 

删除语句产生了差不多11M的日志量

观察插入记录产生了多少redo

SQL> 
SQL> insert into t select * from dba_objects;

73264 rows created.

SQL> select * from v_redo_size;

NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     VALUE
----------
redo size
  34265784


SQL> select 34265784 - 22920220 from dual;

34265784-22920220
-----------------
         11345564

SQL> 

观察更新记录产生了多少redo

SQL> 
SQL> update t set object_id=rownum;

73264 rows updated.

SQL> select * from v_redo_size;

NAME
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     VALUE
----------
redo size
  47178732


SQL> select 47178732 - 34265784 from dual;

47178732-34265784
-----------------
         12912948

SQL> 

更新语句产生了差不多12M的redo

三个试验说明了对表的更新操作无论是删除、插入还是修改都会产生日志.

虽说安全第一不过在某些特定的场合某些表的记录只是作为中间结果临时运算而根本无须永久保留这些表无须写日志那就既高效又安全了

delete无法释放空间

实际上工作中不少性能问题都和delete操作有关。

原因是delete是最耗性能的操作产生的undo最多而且因为undo需要redo来保护的缘故delete产生的redo量也最大。所以不少性能问题都和delete操作有关。

观察未删除表时产生的逻辑读

SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table t purge;

Table dropped.

SQL>
SQL> create table t as select * from dba_objects;

Table created.

SQL> set autotrace on
SQL> select count(*) from t;

  COUNT(*)
----------
     73263


Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |   397   (1)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T    | 73263 |   397   (1)| 00:00:01 |
-------------------------------------------------------------------


Statistics
----------------------------------------------------------
         75  recursive calls
          0  db block gets
       1546  consistent gets
       1422  physical reads
          0  redo size
        552  bytes sent via SQL*Net to client
        384  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         20  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

用delete命令删除t所有记录后逻辑读发生了微小的变化

SQL> 
SQL> set autotrace off
SQL> delete from t;

73263 rows deleted.

SQL> commit;

Commit complete.

SQL> set autotrace on
SQL> select count(*) from t;

  COUNT(*)
----------
         0


Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |   397   (1)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T    | 73263 |   397   (1)| 00:00:01 |
-------------------------------------------------------------------


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
       1426  consistent gets
          0  physical reads
          0  redo size
        549  bytes sent via SQL*Net to client
        384  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> 

使用truncate命令清空表后逻辑读终于大幅度下降了。

SQL> 
SQL> set autotrace off
SQL> truncate table t;

Table truncated.

SQL> set autotrace on
SQL> select count(*) from t;

  COUNT(*)
----------
         0


Execution Plan
----------------------------------------------------------
Plan hash value: 2966233522

-------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |   397   (1)| 00:00:01 |
|   1 |  SORT AGGREGATE    |      |     1 |            |          |
|   2 |   TABLE ACCESS FULL| T    | 73263 |   397   (1)| 00:00:01 |
-------------------------------------------------------------------


Statistics
----------------------------------------------------------
          1  recursive calls
          1  db block gets
          3  consistent gets
          0  physical reads
        104  redo size
        549  bytes sent via SQL*Net to client
        384  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>

delete 删除并不能释放空间虽然delete将很多块的记录删除了但是空块依然保留Oracle在查询时依然会去查询这些空块。而truncate是一种释放高水平位的动作这些空块被回收空间也被释放了。

不过truncate显然不能替代delete因为truncate是一种DDL操作而非DML操作truncate后面是不能带条件的即truncate table t where…是不允许的。但是如果表中这些where条件能形成有效的分区Oracle是支持在分区表中做truncate分区的命令大致为 alter table t truncate partition '分区名'如果where 条件就是分区条件那等同于换个角度实现了 truncate table t where…的功能。

这就是分区表最实用的功能之一了高效地清理数据释放空间续章节中详细描述分区表的特性。

表记录太多检索较慢

有没有什么好方法能提升检索的速度呢主要思路就是缩短访问路径来完成同样的更新查询操作。简单地说完成同样的需求访问块的个数越少越好。Oracle 为了尽可能减少访问路径提供了两种主要技术一种是索引技术另一种则是分区技术。

首先说索引这是Oracle中最重要也最实用的技术之一。在本例中如果created>=xxx and created <=xxx返回的记录非常少或者说和T表的总记录相比非常少则在created列建索引能极大提升该语句的效率。比如我们创建了一个idx_t索引在用该SQL语句查询时首先会访问idx_t这个新建出来的索引段然后通过索引段和表段的映射关系迅速从表中获取行列的信息并返回结果。具体技术细节将会在后续的索引章节中做详细的描述大家要记得索引本身也是一把双刃剑既能给数据库开发应用带来极大的帮助也会给数据库带来不小的灾难。

减少访问路径的第二种技术就是分区。我们把普通表T改造为分区表以created 这个时间列为分区字段从2010年1月到2012年12月按月建36个分区。早先的T表就有一个T段现在情况发生了变化从1个大段分解成了36个小段分别存储了2010年1月到2012年12月的信息。此时假如created>=xxx and created <=xxx 这个时间跨度正好落在2012年11月那Oracle的检索只要完成一个小段的遍历即可假设这36个小段比较均匀我们可大致将其理解为访问量只有原来的三十六分之一大幅减少了访问路径从而高效地提升了性能。

索引本身也是一把双刃剑既能给数据库开发应用带来极大的帮助也会给数据库带来不小的灾难。

分区表除了之前描述的具有高效清理数据的功能外还有减少访问路径的神奇本领。

索引回表读开销很大

观察TABLE ACCESS BY INDEX ROWID 产生的开销

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table t purge;

Table dropped.

SQL> create table t as select * from dba_objects where rownum<=200;

Table created.

SQL> create index idx_obj_id on t(object_id);

Index created.

SQL> set linesize 1000
SQL> set autotrace traceonly
SQL> select * from t where object_id<=10;

9 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 3784017797

--------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |            |     9 |   927 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T          |     9 |   927 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_OBJ_ID |     9 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_ID"<=10)


Statistics
----------------------------------------------------------
         51  recursive calls
        126  db block gets
         54  consistent gets
          3  physical reads
      25504  redo size
       3584  bytes sent via SQL*Net to client
        397  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          9  rows processed

SQL>

一般来说根据索引来检索记录会有一个先从索引中找到记录再根据索引列上的ROWID定位到表中从而返回索引列以外的其他列的动作这就是TABLE ACCESS BY INDEX ROWID。

观察消除TABLE ACCESS BY INDEX ROWID后的开销情况

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> select object_id from t where object_id <= 10;

9 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 188501954

-------------------------------------------------------------------------------
| Id  | Operation        | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |            |     9 |    36 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| IDX_OBJ_ID |     9 |    36 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("OBJECT_ID"<=10)


Statistics
----------------------------------------------------------
        219  recursive calls
          0  db block gets
        179  consistent gets
          0  physical reads
          0  redo size
        661  bytes sent via SQL*Net to client
        628  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         11  sorts (memory)
          0  sorts (disk)
          9  rows processed

SQL> 

因为语句由 select * from t where object_id<=10改写为 select object_id from t where object_id<=10了不用从索引中回到表中获取索引列以外的其他列了。

有序插入却难有序读出

测试表记录顺序插入却难保证顺序读出

[oracle@oracle-db-19c ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Jan 5 10:11:06 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> connect maxwellpan/maxwellpan@PDB1;
Connected.
SQL> 
SQL> show user
USER is "MAXWELLPAN"
SQL> 
SQL> drop table t purge;

Table dropped.

SQL> create table t 
  2  (a int,
  3   b varchar2(4000) default rpad('*',4000,'*'),
  4   c varchar2(4000) default rpad('*',3000,'*'));

Table created.

SQL>  insert into t(a) values(1);
 
 insert into t(a) values(2);
 
 

1 row created.

SQL> SQL> 
1 row created.

SQL> SQL> SQL>  insert into t(a) values(3);

1 row created.

SQL> select A from t;

         A
----------
         1
         2
         3

SQL> delete from t where a=2;

1 row deleted.

SQL>  insert into t(a) values(4);

1 row created.

SQL> commit;

Commit complete.

SQL> select A from t;

         A
----------
         1
         4
         3

SQL> 

比较有无order by 语句在执行计划、开销上的差异

可以观察到有排序的操作的统计信息模块有一个1 sortsmemory表示发生了排序执行计划中也有SORT ORDER BY关键字不过最重要的是没排序的操作代价为3有排序的操作代价为4性能上是有差异的这在数量大的时候将会非常明显。

关于order by 避免排序的方法有两种思路。第一种思路是在order by 的排序列建索引为什么可以消除排序呢这就当成一个悬念在后续介绍索引的章节中给大家揭秘。第二种方法就是将普通表改造为有序散列聚簇表这样可以保证顺序插入order by 展现时无须再有排序动作

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> set linesize 1000
SQL> set autotrace traceonly
SQL> select A from t;


Execution Plan
----------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     3 |    39 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| T    |     3 |    39 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        605  bytes sent via SQL*Net to client
        377  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          3  rows processed

SQL> 
SQL> select A from t order by A;


Execution Plan
----------------------------------------------------------
Plan hash value: 961378228

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     3 |    39 |     4  (25)| 00:00:01 |
|   1 |  SORT ORDER BY     |      |     3 |    39 |     4  (25)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T    |     3 |    39 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
         26  recursive calls
          0  db block gets
         10  consistent gets
          0  physical reads
          0  redo size
        605  bytes sent via SQL*Net to client
        609  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          3  rows processed

SQL> 

3.奇特的全局临时表

 1.分析全局临时表的类型

全局临时表分为两种类型一种是基于会话的全局临时表on commit preserve rows一种是基于事务的全局临时表on commit delete rows

创建基于事务和会话的全局临时表

SQL> 
SQL> show user
USER is "MAXWELLPAN"
SQL> 
SQL> drop table t_tmp_session purge;
drop table t_tmp_session purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> drop table t_tmp_transaction purge;
drop table t_tmp_transaction purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> create global temporary table t_tmp_session on commit preserve rows as select * from dba_objects where 1=2;

Table created.

SQL>
SQL> col table_name format a50
SQL> col tem format a50
SQL> col duration format a50
SQL> select table_name,temporary,duration from user_tables where table_name='T_TMP_SESSION';

TABLE_NAME                                         TEM DURATION
-------------------------------------------------- --- --------------------------------------------------
T_TMP_SESSION                                      Y   SYS$SESSION

SQL> create global temporary table t_tmp_transaction on commit delete rows as select * from dba_objects where 1=2;

Table created.

SQL> select table_name,temporary,duration from user_tables where table_name='T_TMP_TRANSACTION';

TABLE_NAME                                         TEM DURATION
-------------------------------------------------- --- --------------------------------------------------
T_TMP_TRANSACTION                                  Y   SYS$TRANSACTION

SQL> 

上述命令完成了基于会话的全局临时表 T_TMP_SESSION 和基于事务的全局临时表T_TMP_TRANSACTION。接下来大家肯定想知道DML操作针对全局临时表产生的日志和针对普通表有什么不同.

2.观察各类DML的redo量

分别观察两种全局临时表针对各类DML语句产生的redo量

create or replace view v_redo_size as
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic#=b.statistic#
and a.name='redo size';
SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size                240504

SQL> insert into t_tmp_transaction select * from dba_objects;

73266 rows created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size                774580

SQL> select 774580-240504 from dual;

774580-240504
-------------
       534076

SQL> insert into t_tmp_session select * from dba_objects;

73266 rows created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size               1308488

SQL> select 1308488 - 774580 from dual;

1308488-774580
--------------
        533908

SQL> update t_tmp_transaction set object_id=rownum;

73266 rows updated.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size               7788968

SQL> select 7788968 - 1308488 from dual;

7788968-1308488
---------------
        6480480

SQL> update t_tmp_session set object_id=rownum;

73266 rows updated.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              12430324

SQL> select 12430324 - 7788968 from dual;

12430324-7788968
----------------
         4641356

SQL> delete from t_tmp_session;

73266 rows deleted.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              23330180

SQL> select 23330180 - 12430324 from dual;

23330180-12430324
-----------------
         10899856

SQL> delete from t_tmp_transaction;

73266 rows deleted.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              34230036

SQL> select 34230036 - 23330180 from dual;

34230036-23330180
-----------------
         10899856

SQL> 

全局临时表和普通表产生日志情况的比较

SQL> 
SQL> drop table t purge;

Table dropped.

SQL> show user;
USER is "MAXWELLPAN"
SQL> create table t as select * from dba_objects where 1=2;

Table created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              34301144

SQL> insert into t select * from dba_objects;

73266 rows created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              45974392

SQL> select 45974392 - 34301144 from dual;

45974392-34301144
-----------------
         11673248

SQL> update t set object_id=rownum;

73266 rows updated.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              57139632

SQL> select 57139632 - 45974392 from dual;

57139632-45974392
-----------------
         11165240

SQL> delete from t;

73266 rows deleted.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              68186560

SQL> select 68186560 - 57139632 from dual;

68186560-57139632
-----------------
         11046928

SQL> 

通过简单的比较我们即可得出结论无论插入、更新还是删除操作普通表产生的日志都比全局临时表要多。

DML操作针对全局临时表来说只是产生的日志要少得多而不是不会产生。

3.全局临时表的两大重要特性

1.高效删除记录

全局临时表有两个重要的特点。一是高效删除记录基于事务的全局临时表 COMMIT 或者 SESSION 连接退出后临时表记录自动删除基于会话的全局临时表则是SESSION连接退出后临时表记录自动删除都无须我们手动去操作。二是针对不同会话数据独立不同的SESSION访问全局临时表看到的结果不同。

基于事务的全局临时表的高效删除

SQL> 
SQL> select count(*) from t_tmp_transaction;

  COUNT(*)
----------
         0

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              68186560

SQL> insert into t_tmp_transaction select * from dba_objects;

73266 rows created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              68720580

SQL> select 68720580 - 68186560 from dual;

68720580-68186560
-----------------
           534020

SQL> commit;

Commit complete.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              68720756

SQL> select count(*) from t_tmp_transaction;

  COUNT(*)
----------
         0

SQL> 
SQL> select 68720756 - 68720580 from dual;

68720756-68720580
-----------------
              176

SQL> ---commit 方式删除全局临时表记录所产生的日志量是176B
SQL>

基于会话的全局临时表COMMIT并不清空记录

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              68720756

SQL> insert into t_tmp_session select * from dba_objects;

73266 rows created.

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              69268792

SQL> select 69268792 - 68720756 from dual;

69268792-68720756
-----------------
           548036

SQL> commit;

Commit complete.

SQL> select count(*) from t_tmp_session;

  COUNT(*)
----------
     73266

SQL> select * from v_redo_size;

NAME                      VALUE
-------------------- ----------
redo size              69268940

SQL> select 69268940 - 69268792 from dual;

69268940-69268792
-----------------
              148

SQL> 

退出基于事务的全局临时表后再登入观察记录情况

SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
[oracle@oracle-db-19c ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Jan 5 11:22:30 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> connect maxwellpan/maxwellpan@PDB1;
Connected.
SQL> show user;
USER is "MAXWELLPAN"
SQL> select count(*) from t_tmp_session;

  COUNT(*)
----------
         0

SQL> 

如果全局临时表在程序的一次调用执行过程中需要多次清空记录再插入记录就要考虑用基于事务的这时COMMIT可以把结果快速清理了否则用delete效率低下。如果不存在这种情况就用基于会话的更简单连COMMIT的动作都省了。

一般来说基于会话的全局临时表的应用会更多一些少数比较复杂的应用涉及一次调用中需要清空记录再插入等复杂动作时才考虑用基于事务的全局临时表。

2.不同会话独立

基于全局临时表的会话独立性的观察——第1个会话

SQL> show user;
USER is "MAXWELLPAN"
SQL> select * from v$mystat where rownum=1;

       SID STATISTIC#      VALUE     CON_ID
---------- ---------- ---------- ----------
        18          0          0          3

SQL> select * from t_tmp_session;

no rows selected

SQL> insert into t_tmp_session select * from dba_objects;

73266 rows created.

SQL> commit;

Commit complete.

SQL> select count(*) from t_tmp_session;

  COUNT(*)
----------
     73266

SQL> 

基于全局临时表的会话独立性的观察——第2个会话

[root@oracle-db-19c ~]# su - oracle
[oracle@oracle-db-19c ~]$ 
[oracle@oracle-db-19c ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Thu Jan 5 11:30:28 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> connect maxwellpan/maxwellpan@PDB1;
Connected.
SQL> show user;
USER is "MAXWELLPAN"
SQL> select * from v$mystat where rownum=1;

       SID STATISTIC#      VALUE     CON_ID
---------- ---------- ---------- ----------
       143          0          0          3

SQL> select count(*) from t_tmp_session;

  COUNT(*)
----------
         0

SQL> insert into t_tmp_session select * from dba_objects where rownum=1;

1 row created.

SQL> commit;

Commit complete.

SQL> select count(*) from t_tmp_session;

  COUNT(*)
----------
         1

SQL> 

这是一个神奇的特性结合高效删除灵活应用这两个特性将会给相关工作带来巨大的帮助。大家将会在后续的课程中感受到。

4.神通广大的分区表

学习表设计过程中极其重要的一项技术分区表设计

 表记录太大检索慢和delete 删除有瑕疵这两个缺点正好可以被分区表的分区消除和高效清理分区数据这两大特点给弥补了

“全民搜索”的时代语法和知识点都不是问题搜不到的是体系是重点是思想。

4.1分区表的类型及原理

分区表的类型有范围分区、列表分区、HASH分区及组合分区4种其中范围分区应用最为广泛需要重点学习和掌握。而列表分区次之在某些场合下也可以考虑使用组合分区在Oracle 11g以前组合分区的组合方式比较有限。相对而言HASH分区在应用中适用的场景并不广泛使用的频率比较低.

技术其实并不难最难的是如何选择。

分区表的使用先了解分区表的建立方法

1.范围分区

记住范围分区最常见的是按时间列进行分区。

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table range_part_tab purge;
drop table range_part_tab purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> create table range_part_tab (id number, deal_date date, area_code number,contents varchar2(4000))
  2           partition by range(deal_date)
  3           (
  4           partition p1 values less than (TO_DATE('2023-02-01','YYYY-MM-DD')),
  5           partition p2 values less than (TO_DATE('2023-03-01','YYYY-MM-DD')),
  6           partition p3 values less than (TO_DATE('2023-04-01','YYYY-MM-DD')),
  7           partition p4 values less than (TO_DATE('2023-05-01','YYYY-MM-DD')),
  8           partition p5 values less than (TO_DATE('2023-06-01','YYYY-MM-DD')),
  9           partition p6 values less than (TO_DATE('2023-07-01','YYYY-MM-DD')),
 10           partition p7 values less than (TO_DATE('2023-08-01','YYYY-MM-DD')),
 11           partition p8 values less than (TO_DATE('2023-09-01','YYYY-MM-DD')),
 12           partition p9 values less than (TO_DATE('2023-10-01','YYYY-MM-DD')),
 13           partition p10 values less than (TO_DATE('2023-11-01','YYYY-MM-DD')),
 14           partition p11 values less than (TO_DATE('2023-12-01','YYYY-MM-DD')),
 15           partition p12 values less than (TO_DATE('2024-01-01','YYYY-MM-DD')),
 16           partition p_max values less than (maxvalue)
 17           );

Table created.

SQL> --以下是插入2023年一整年日期随机数和表示福建地区号含义591到599的随机数记录并有10万条如下
SQL> insert into range_part_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

SQL> commit;

Commit complete.

SQL> 

以上操作完成了范围分区的分区表创建并且构造出10万条记录并插入到分区表中注意如下5点

① 范围分区的关键字为partition by range即这三个关键字表示该分区为范围分区。

② values less than是范围分区特定的语法用于指明具体的范围比如partition p2 values less thanTO_DATE'2023-03-01','YYYY-MM-DD'表示小于3月份的记录。

③ partition p1 到partition p_max表示总共建立了13个分区。

④ 最后还要注意partition p_max values less thanmaxvalue的部分表示超出这些范围的记录全部落在这个分区中免得出错。

⑤ 分区表中的分区可分别指定在不同的表空间里如果不写即为都在同一默认表空间里。

2.列表分区

列表分区最常见的分区列就是以地区列作为分区

列表分区示例如下

SQL> 
SQL> show user
USER is "MAXWELLPAN"
SQL> drop table list_part_tab purge;
drop table list_part_tab purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> -- Note: 此分区为列表分区
SQL> create table list_part_tab (id number, deal_date date, area_code number,contents varchar2(4000))
  2           partition by list(area_code)
  3           (
         partition p_591 values (591),
  5           partition p_592 values (592),
  6           partition p_593 values (593),
  7           partition p_594 values (594),
  8           partition p_595 values (595),
  9           partition p_596 values (596),
 10           partition p_597 values (597),
 11           partition p_598 values (598),
 12           partition p_599 values (599),
 13           partition p_other values (DEFAULT)
 14           );

Table created.

SQL> 
SQL> -- 以下是插入2023年一整年日期随机数和表示福建地区号含义591到599的随机数记录共有10万条如下
SQL> 
SQL> 
SQL> insert into list_part_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

SQL> commit;

Commit complete.

SQL> 

需要注意

① 列表分区的关键字为partition by list即这三个关键字表示该分区为列表分区

② 不同于之前范围分区的values less than列表分区仅需values 即可确定范围。值得注意的是partition p_592 values592并不是说明取值只能写一个也可写为多个比如partition p_union values592,593,594

③ partition p_591 到partition p_other表示总共建立了10个分区

④ 最后还要注意partition p_other valuesDEFAULT部分表示不在刚才591到599范围的记录全部落在这个默认分区中避免应用出错

分区表的分区可分别指定在不同的表空间里如果不写即为都在同一默认表空间里

3.散列分区Hash分区

建散列分区时所取的列故意是和之前范围分区相同的列都是时间列

散列分区示例如下

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table has_part_tab purge;
drop table has_part_tab purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> --注意此分区为散列分区
SQL> 
SQL> create table hash_part_tab (id number, deal_date date, area_code number,contents varchar2(4000))
  2           partition by hash(deal_date)
  3           partitions 12
  4           ;

Table created.

SQL> -- 以下是插入2023年一整年日期随机数和表示福建地区号含义591到599的随机数记录共有10万条如下
SQL> insert into hash_part_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

SQL> commit;

Commit complete.

SQL>

Note:

① 散列分区的关键字为partition by hash出现这三个关键字即表示当前分区为散列分区

散列分区与之前两种分区的明显差别在于没有指定分区名而仅仅是指定了分区个数如PARTITIONS 12。

③ 散列分区的分区个数尽量设置为偶数个比如本例中是12个如果是11个或者13个就不妥了具体原因和Oracle内部架构有关.

可以指定散列分区的分区表空间比如增加如下一小段STORE INts1,ts2,ts3,ts4,t5,t6,t7,t8,t9,t10,t11,t12表示分别存放在12个不同的表空间里当然不写出表空间就是都存放在同一默认表空间里。

4.组合分区

在 Oracle 11g 以前主要支持 range-list 和range-hash这两种组合在实际应用中最常用的组合是范围-列表range-list的组合。

组合分区示例

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table range_list_part_tab purge;
drop table range_list_part_tab purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> --注意此分区为范围分区
SQL> create table range_list_part_tab(id number, deal_date date, area_code number,contents varchar2(4000))
  2            partition by range(deal_date)
  3            subpartition by list(area_code)
  4            subpartition template
  5            (subpartition p_591 values (591),
  6             subpartition p_592 values (592),
  7             subpartition p_593 values (593),
  8             subpartition p_594 values (594),
  9             subpartition p_595 values (595),
 10             subpartition p_596 values (596),
 11             subpartition p_597 values (597),
 12             subpartition p_598 values (598),
 13             subpartition p_599 values (599),
 14             subpartition p_other values (DEFAULT))
 15             (
 16             partition p1 values less than (TO_DATE('2023-02-01','YYYY-MM-DD')),
 17             partition p2 values less than (TO_DATE('2023-03-01','YYYY-MM-DD')),
 18             partition p3 values less than (TO_DATE('2023-04-01','YYYY-MM-DD')),
 19             partition p4 values less than (TO_DATE('2023-05-01','YYYY-MM-DD')),
 20             partition p5 values less than (TO_DATE('2023-06-01','YYYY-MM-DD')),
 21             partition p6 values less than (TO_DATE('2023-07-01','YYYY-MM-DD')),
 22             partition p7 values less than (TO_DATE('2023-08-01','YYYY-MM-DD')),
 23             partition p8 values less than (TO_DATE('2023-09-01','YYYY-MM-DD')),
 24             partition p9 values less than (TO_DATE('2023-10-01','YYYY-MM-DD')),
 25             partition p10 values less than (TO_DATE('2023-11-01','YYYY-MM-DD')),
           partition p11 values less than (TO_DATE('2023-12-01','YYYY-MM-DD')),
 27             partition p12 values less than (TO_DATE('2024-01-01','YYYY-MM-DD')),
 28             partition p_max values less than (maxvalue)
 29             );

Table created.

SQL> -- 以下是插入2023年一整年日期随机数和表示福建地区号含义591到599的随机数记录共有10万条如下
SQL> insert into range_list_part_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

SQL> commit;

Commit complete.

SQL>

主要就是增加了subpartition by listarea_code这个模块其他部分和原先的范围分区没什么差异。

Note:

① 组合分区是由主分区和从分区组成的比如范围-列表分区主分区是范围分区而从分区是列表分区从分区的关键字为subpartition如本例中的subpartition by listarea_code。

② 为了避免在每个主分区中都写相同的从分区可以考虑用模板的方式比如本例中的subpartition TEMPLATE关键字。

③ 只要涉及子分区模块都需要有subpartition关键字。

④ 关于表空间和之前的没有差别依然是可以指定也可以不指定。

Oracle 11g以前除了上述范围-列表这个最常见的组合分区外还有range-hash组合。在Oracle 11g后还提供了range-range、list-range、list-hash和list-list这4种组合本次课程我就主要介绍range-list分区其他的适用场合相对比较少使用频率较低。

5.分区原理

4种不同类型的分区表并依次插入了相同的10万条记录现在我们再建一张普通表也插入相同的10万条记录。

分区原理分析之普通表插入

SQL> 
SQL> drop table norm_tab_purge;
drop table norm_tab_purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


SQL> create table norm_tab (id number, deal_date date, area_code number,contents varchar2(4000));

Table created.

SQL> 
SQL> insert into norm_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

SQL> commit;

Commit complete.

SQL>

分区原理分析之普通表与分区表在段分配上的差异

SQL> 
SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> set linesize  666
SQL> set pagesize 5000
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segement_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "字节数(M)",
  5         tablespace_name
  6   from user_segments
  7   where segment_name in ('RANGE_PART_TAB','NORM_TAB');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                                            字节数(M) TABLESPACE_NAME
-------------------- -------------------- ------------------------------------------------------ ---------- ------------------------------------------------------------------------------------------
NORM_TAB                                  TABLE                                                          47 TBS_MAXWELL
RANGE_PART_TAB       P1                   TABLE PARTITION                                                48 TBS_MAXWELL

SQL> 

观察HASH分区的段分配情况

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> set linesize  666
SQL> set pagesize 5000
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segement_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "字节数(M)",
  5         tablespace_name
  6   from user_segments
  7   where segment_name in ('HASH_PART_TAB');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                                            字节数(M) TABLESPACE_NAME
-------------------- -------------------- ------------------------------------------------------ ---------- ------------------------------------------------------------------------------------------
HASH_PART_TAB        SYS_P741             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P742             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P743             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P744             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P745             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P746             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P747             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P748             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P749             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P750             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P751             TABLE PARTITION                                                 8 TBS_MAXWELL
HASH_PART_TAB        SYS_P752             TABLE PARTITION                                                 8 TBS_MAXWELL

12 rows selected.

SQL> 

其实HASH分区最大的好处在于它可将数据根据一定的HASH算法均匀分布到不同的分区中去避免查询数据时集中在某一个地方从而避免热点块的竞争改善IO此处在时间列建HASH分区一般是不妥当的因为我们经常都是指定具体的时间来完成数据检索或者是指定具体的时间来完成数据清理这对HASH分区来说就很不适合了。此外大家还要注意一点HASH可以精确匹配无法范围扫描。

观察组合分区的段分配的个数

SQL> 
SQL> select count(*)
  2    from user_segments
  3  where segment_name='RANGE_LIST_PART_TAB';

  COUNT(*)
----------
         9

SQL> 

大家记住分区表也是有额外开销的如果分区数量过多Oracle 就需要管理过多的段在操作分区表时也容易引发Oracle内部大量的递归调用此外本身的语法也有一定的复杂度。所以一般来说只有大表才建议建分区记录数在100万以下的表基本不建议建分区。

4.2.4 分区表最实用的特性

1.高效的分区消除

“分区表存在的最大意义在于可以有效地做到分区消除比如你对地区号做了分区查询福州就只会在福州的分区中查找数据而不会到厦门、漳州、泉州等其他分区中查找这就是分区消除消除了福州以外的所有其他分区。

观察范围分区表的分区消除带来的性能优势

[oracle@oracle-db-19c ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Jan 7 14:23:42 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> conn maxwellpan/maxwellpan@PDB1
Connected.
SQL> 
SQL> set linesize 1000
SQL> set autotrace traceonly
SQL> set timing on
SQL> select *
  2    from range_part_tab
  3  where deal_date >= TO_DATE('2023-09-04','YYYY-MM-DD')
  4    and deal_date <= TO_DATE('2023-09-07','YYYY-MM-DD');

no rows selected

Elapsed: 00:00:00.06

Execution Plan
----------------------------------------------------------
Plan hash value: 16125146

---------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name           | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                |   274 |   111K|   128   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|                |   274 |   111K|   128   (0)| 00:00:01 |     9 |     9 |
|*  2 |   TABLE ACCESS FULL    | RANGE_PART_TAB |   274 |   111K|   128   (0)| 00:00:01 |     9 |     9 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("DEAL_DATE">=TO_DATE(' 2023-09-04 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "DEAL_DATE"<=TO_DATE(' 2023-09-07 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))


Statistics
----------------------------------------------------------
        259  recursive calls
         22  db block gets
        312  consistent gets
          5  physical reads
       4276  redo size
        582  bytes sent via SQL*Net to client
        489  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
         16  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL> 

普通表无法用deal_date条件进行分区消除

SQL> 
SQL> select *
  2    from norm_tab
  3  where deal_date >= TO_DATE('2023-09-04','YYYY-MM-DD')
  4    and deal_date <= TO_DATE('2023-09-07','YYYY-MM-DD');

no rows selected

Elapsed: 00:00:00.21

Execution Plan
----------------------------------------------------------
Plan hash value: 278673677

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   274 |   111K|  1606   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| NORM_TAB |   274 |   111K|  1606   (1)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("DEAL_DATE">=TO_DATE(' 2023-09-04 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss') AND "DEAL_DATE"<=TO_DATE(' 2023-09-07 00:00:00',
              'syyyy-mm-dd hh24:mi:ss'))


Statistics
----------------------------------------------------------
         52  recursive calls
         21  db block gets
       5976  consistent gets
       5915  physical reads
       3980  redo size
        582  bytes sent via SQL*Net to client
        483  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL> 

同样的语句查询有相同记录的表分区表的查询代价仅为128逻辑读仅为312而普通表的代价却为1606逻辑读为5976性能方面有着天壤之别。

差别如此之大应该是和分区表查询只遍历了13个分区中的一个有关。在分区表查询的执行计划中我看到p_start和p_stop都标记上9表示只遍历了第9个分区。这样避开了对其余12个分区的查询这就是所谓的分区消除。

观察范围-列表分区表的分区条件的分区消除

SQL> set autotrace traceonly
SQL> set linesize 1000
SQL> select *
  2    from range_list_part_tab
  3  where deal_date >= TO_DATE('2023-09-04','YYYY-MM-DD')
  4    and deal_date <= TO_DATE('2023-09-07','YYYY-MM-DD')
  5  and area_code=591;

no rows selected

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 406789865

--------------------------------------------------------------------------------------------------------------
| Id  | Operation              | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |                     |    30 | 12540 |  2457   (1)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE SINGLE|                     |    30 | 12540 |  2457   (1)| 00:00:01 |     9 |     9 |
|   2 |   PARTITION LIST SINGLE|                     |    30 | 12540 |  2457   (1)| 00:00:01 |   KEY |   KEY |
|*  3 |    TABLE ACCESS FULL   | RANGE_LIST_PART_TAB |    30 | 12540 |  2457   (1)| 00:00:01 |    81 |    81 |
--------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("DEAL_DATE">=TO_DATE(' 2023-09-04 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND
              "DEAL_DATE"<=TO_DATE(' 2023-09-07 00:00:00', 'syyyy-mm-dd hh24:mi:ss'))


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        582  bytes sent via SQL*Net to client
        512  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL>

普通表无法用area_code条件进行分区消除

SQL> 
SQL> select *
  2    from norm_tab
  3  where deal_date >= TO_DATE('2023-09-04','YYYY-MM-DD')
  4    and deal_date <= TO_DATE('2023-09-07','YYYY-MM-DD')
  5  and area_code=591;

no rows selected

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 278673677

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |    30 | 12540 |  1606   (1)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| NORM_TAB |    30 | 12540 |  1606   (1)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("DEAL_DATE">=TO_DATE(' 2023-09-04 00:00:00', 'syyyy-mm-dd
              hh24:mi:ss') AND "AREA_CODE"=591 AND "DEAL_DATE"<=TO_DATE(' 2023-09-07
              00:00:00', 'syyyy-mm-dd hh24:mi:ss'))


Statistics
----------------------------------------------------------
         18  recursive calls
          0  db block gets
       5954  consistent gets
       5914  physical reads
          0  redo size
        582  bytes sent via SQL*Net to client
        722  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          4  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL>

组合分区导致范围定位得更小了产生了0个逻辑读比之前的312个逻辑读还要少。至于普通表的查询因为始终是全表扫描所以逻辑读依然和之前差不多达到5954个。

总得来说 分区表应用在大表上更合适至少要大于100万条记录的表方可考虑。

2.强大的分区操作

1分区truncate好快捷

“大家还记得在描述普通堆表时老师说过delete 无法释放空间而truncate却有效地释放了空间。但可惜的是针对普通表而言truncate往往不能轻易使用因为delete往往可针对某些条件进行局部记录删除truncate显然不能带上条件无法做到局部删除。这时分区表就能发挥作用了。Oracle可以实现只truncate某个分区这就等同于实现了局部删除

分区清除的例子

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL>  delete from norm_tab
  2  where deal_date >= TO_DATE('2022-09-01','YYYY-MM-DD')
  3    and deal_date <= TO_DATE('2022-09-30','YYYY-MM-DD');

8297 rows deleted.

Elapsed: 00:00:00.44
SQL> rollback;

Rollback complete.

Elapsed: 00:00:00.07
SQL> 
SQL> alter table range_part_tab truncate partition p9;

Table truncated.

Elapsed: 00:00:00.32
SQL> select count(*) from range_part_tab
  2  where deal_date >= TO_DATE('2022-09-01','YYYY-MM-DD')
  3    and deal_date <= TO_DATE('2022-09-30','YYYY-MM-DD');

  COUNT(*)
----------
      8268

Elapsed: 00:00:00.21
SQL> 

说明针对普通表我们只能delete而针对分区表需求等同于要删除p9的分区然后直接alter table range_part_tab truncate partition p9就完成了。关于truncate 可以释放表空间之前已经证明过了现在无须再次证明。

分区清理的方法在有大量历史数据需要清理的时候发挥着极其重要的作用。很多历史表、日志表都被设计为分区表正是由于这个特性使得清理数据极其方便迅速而且能有效释放空间。

2分区数据转移很神奇

“关于分区表的历史记录的处理其实是可以分成删除和转移两部分的关于转移备份的方案Oracle 提供了一个非常棒的工具就是分区交换可以实现普通表和分区表的某个分区之间数据的相互交换它们之间的交换非常快基本上瞬间就可以完成实际上只是Oracle在内部数据字典中做的一些小改动而已。命令很简单类似alter table 分区表 exchange partition 分区名 with table中间表。其中的关键字为exchange和with table 等。

分区交换的神奇例子

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> drop table mid_tab_purge;
drop table mid_tab_purge
           *
ERROR at line 1:
ORA-00942: table or view does not exist


Elapsed: 00:00:00.04
SQL> create table mid_table(id number,deal_date date,area_code number,contents varchar2(4000));

Table created.

Elapsed: 00:00:00.10
SQL> select count(*) from mid_table;

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL>

QL> select count(*) from range_part_tab
  2  where deal_date >= TO_DATE('2022-08-01','YYYY-MM-DD')
  3    and deal_date <= TO_DATE('2022-08-31','YYYY-MM-DD');

  COUNT(*)
----------
      8478

Elapsed: 00:00:00.03
SQL>

SQL> select count(*) from range_part_tab partition(p8);

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL> alter table range_part_tab exchange partition p8 with table mid_table;

Table altered.

Elapsed: 00:00:00.90
SQL> select count(*) from range_part_tab partition(p8);

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL> select count(*) from mid_table;

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL> alter table range_part_tab exchange partition p1 with table mid_table;

Table altered.

Elapsed: 00:00:00.05
SQL> select count(*) from range_part_tab partition(p1);

  COUNT(*)
----------
         0

Elapsed: 00:00:00.01
SQL> select count(*) from mid_table;

  COUNT(*)
----------
    100000

Elapsed: 00:00:00.02
SQL> 

由于数据的问题当前使用p1分区与普通表mid_table进行交换。实现相同的结果如上所示

分区交换能否从普通表交换到分区表

分区交换从普通表交换到分区表

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> 
SQL> alter table range_part_tab exchange partition p1 with table mid_table;

Table altered.

Elapsed: 00:00:00.12
SQL> select count(*) from range_part_tab partition(p1);

  COUNT(*)
----------
    100000

Elapsed: 00:00:00.02
SQL> select count(*) from mid_table;

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL> 

不过这里还要注意exchange是交换的含义试验中mid_table表开始没有记录所以交换过程中总有一边记录为0。如果mid_table表初始有记录比如有1条那exchange的结果就是一边是100000条另一边是1条的交替变换了


3分区切割你想分就分

分区切割

SQL> 
SQL> show user
USER is "MAXWELLPAN"
SQL> 
SQL> drop table range_part_tab purge;

Table dropped.

Elapsed: 00:00:00.80
SQL> 
SQL> create table range_part_tab (id number, deal_date date, area_code number,contents varchar2(4000))
  2  partition by range(deal_date)
  3  (
  4  partition p1 values less than (TO_DATE('2022-02-01','YYYY-MM-DD')),
  5  partition p2 values less than (TO_DATE('2022-03-01','YYYY-MM-DD')),
  6  partition p3 values less than (TO_DATE('2022-04-01','YYYY-MM-DD')),
  7  partition p4 values less than (TO_DATE('2022-05-01','YYYY-MM-DD')),
  8  partition p5 values less than (TO_DATE('2022-06-01','YYYY-MM-DD')),
  9  partition p6 values less than (TO_DATE('2022-07-01','YYYY-MM-DD')),
 10  partition p7 values less than (TO_DATE('2022-08-01','YYYY-MM-DD')),
 11  partition p8 values less than (TO_DATE('2022-09-01','YYYY-MM-DD')),
 12  partition p9 values less than (TO_DATE('2022-10-01','YYYY-MM-DD')),
 13  partition p10 values less than (TO_DATE('2022-11-01','YYYY-MM-DD')),
 14  partition p11 values less than (TO_DATE('2022-12-01','YYYY-MM-DD')),
 15  partition p12 values less than (TO_DATE('2023-01-01','YYYY-MM-DD')),
 16  partition p_max values less than (maxvalue)
 17  );

Table created.

Elapsed: 00:00:00.03
SQL> --以下是插入2022年一整年日期随机数和表示福建地区号含义591到599的随机数记录并有10万条如下
SQL> insert into range_part_tab
  2    (id, deal_date, area_code, contents)
  3    select rownum,
  4           to_date(to_char(sysdate - 365, 'J') +
  5                   TRUNC(DBMS_RANDOM.value(0, 365)),
  6                   'J'),
  7           ceil(dbms_random.value(590, 599)),
  8           rpad('*', 400, '*')
  9      from dual
 10    connect by rownum <= 100000;

100000 rows created.

Elapsed: 00:00:02.15
SQL> commit;

Commit complete.

Elapsed: 00:00:00.01
SQL> 
SQL> 
SQL> 
SQL> alter table range_part_tab split partition p_max at (TO_DATE('2023-02-01','YYYY-MM-DD')) into (PARTITION p2023_01,PARTITION p_max);

Table altered.

Elapsed: 00:00:00.39
SQL> alter table range_part_tab split partition p_max at (TO_DATE('2023-03-01','YYYY-MM-DD')) into (PARTITION p2023_02,PARTITION p_max);

Table altered.

Elapsed: 00:00:00.05
SQL> 

可以通过以下实验来查看分区切割是否成功

SQL> set pagesize 200
SQL> set linesize 200
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segment_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "MB",
  5         tablespace_name
  6    from user_segments
  7  where segment_name in ('RANGE_PART_TAB');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                 MB TABLESPACE_NAME
-------------------- -------------------- -------------------- ---------- ------------------------------------------------------------------------------------------
RANGE_PART_TAB       P1                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P10                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P11                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P12                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P2                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P2023_01             TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P2023_02             TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P3                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P4                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P5                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P6                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P7                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P8                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P9                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P_MAX                TABLE PARTITION               8 TBS_MAXWELL

15 rows selected.

Elapsed: 00:00:00.04
SQL>

 Note:

① 分区切割的三个关键字是split、at 和into。

② at部分在此处说明了具体的范围小于某个指定的值。

③ into部分说明分区被切割成两个分区比如intoPARTITION p2023_01,PARTITION P_MAX表示将P_MAX切割成PARTITION p2023_01和PARTITION P_MAX两部分其中括号里的P_MAX可以改为新的名字也可以保留原来的名字。

4分区合并你想合就合

SQL> alter table range_part_tab merge partitions p2023_02,p_max INTO PARTITION p_max;

Table altered.

Elapsed: 00:00:00.13
SQL> alter table range_part_tab merge partitions p2023_01,p_max INTO PARTITION p_max;

Table altered.

Elapsed: 00:00:00.06
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segment_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "MB",
  5         tablespace_name
  6    from user_segments
  7  where segment_name in ('RANGE_PART_TAB');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                 MB TABLESPACE_NAME
-------------------- -------------------- -------------------- ---------- ------------------------------------------------------------------------------------------
RANGE_PART_TAB       P1                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P10                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P11                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P12                  TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P2                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P3                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P4                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P5                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P6                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P7                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P8                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P9                   TABLE PARTITION               8 TBS_MAXWELL
RANGE_PART_TAB       P_MAX                TABLE PARTITION               8 TBS_MAXWELL

13 rows selected.

Elapsed: 00:00:00.04
SQL>

Note:

① 分区合并的关键字是merge和into。

② merge后面跟着的是需要合并的两个分区名。

③ into部分为合并后的分区名可以是新的分区名也可以沿用已存在的分区名。

5分区增与删非常简单

最后一个分区是maxvalue不允许追加只允许分割

SQL> show user
USER is "MAXWELLPAN"
SQL> alter table range_part_tab add partition p2023_01 values less than (TO_DATE('2023-02-01','YYYY-MM-DD'));
alter table range_part_tab add partition p2023_01 values less than (TO_DATE('2023-02-01','YYYY-MM-DD'))
                                         *
ERROR at line 1:
ORA-14074: partition bound must collate higher than that of the last partition


Elapsed: 00:00:00.00
SQL> 

不过我们倒是可以改成先试验分区删除把 p_max 删除了然后追加自然就没问题了继续试验

可以删除maxvalue后再进行分区追加

SQL> 
SQL> alter table range_part_tab drop partition p_max;

Table altered.

Elapsed: 00:00:00.06
SQL> alter table range_part_tab add partition p2023_01 values less than (TO_DATE('2023-02-01','YYYY-MM-DD'));

Table altered.

Elapsed: 00:00:00.01
SQL> alter table range_part_tab add partition p2023_02 values less than (TO_DATE('2023-03-01','YYYY-MM-DD'));

Table altered.

Elapsed: 00:00:00.01
SQL> 

关于分区增删的操作

要注意如下几点

“① 增加分区的关键字是add partition而删除分区的关键字是drop partition。

“② 由于maxvalue分区的存在无法追加新的分区必须删除了才可以追加。

4.2.4 分区索引类型简述

1.全局索引

说到分区表其实还有一个不得不提的重要知识点那就是分区索引。不少分区表设计中正是由于相关索引的错误设计导致分区表遇到诸多问题。不过由于在下一章中老师会非常详尽地给大家全面讲解索引的知识所以本章暂且不会对分区索引做特别详尽的描述只是抓住一些重点向大家说明一下.

分区表的索引一般可以分成两类一类是全局索引另一类是局部索引。其中全局索引和普通的创建索引的方式无异而局部索引需要增加local关键字。

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> ----以下是对deal_date 列建全局索引
SQL> create index idx_part_tab_date on range_part_tab(deal_date);

Index created.

Elapsed: 00:00:00.11
SQL> ----以下是对area_code列建局部索引
SQL> create index idx_part_tab_area on range_part_tab(area_code) local;

Index created.

Elapsed: 00:00:00.12
SQL>

其实全局索引基本上可以理解为普通索引。

全局索引的段分配情况

SQL> 
SQL> set linesize 666
SQL> set pagesize 5000
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segment_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "MB",
  5         tablespace_name
  6    from user_segments
  7  where segment_name in ('IDX_PART_TAB_DATE');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                 MB TABLESPACE_NAME
-------------------- -------------------- -------------------- ---------- ------------------------------------------------------------------------------------------
IDX_PART_TAB_DATE                         INDEX                         3 TBS_MAXWELL

Elapsed: 00:00:00.05
SQL> 

2.局部索引

而局部索引就有差异了。如下

局部索引的段分配情况

SQL> 
SQL> set linesize 666
SQL> set pagesize 5000
SQL> column segment_name format a20
SQL> column partition_name format a20
SQL> column segment_type format a20
SQL> select segment_name,
  2         partition_name,
  3         segment_type,
  4         bytes/1024/1024 "MB",
  5         tablespace_name
  6    from user_segments
  7  where segment_name in ('IDX_PART_TAB_AREA');

SEGMENT_NAME         PARTITION_NAME       SEGMENT_TYPE                 MB TABLESPACE_NAME
-------------------- -------------------- -------------------- ---------- ------------------------------------------------------------------------------------------
IDX_PART_TAB_AREA    P1                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P10                  INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P11                  INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P12                  INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P2                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P3                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P4                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P5                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P6                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P7                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P8                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P9                   INDEX PARTITION           .1875 TBS_MAXWELL
IDX_PART_TAB_AREA    P_MAX                INDEX PARTITION           .0625 TBS_MAXWELL

13 rows selected.

Elapsed: 00:00:00.04
SQL>

局部索引其实就是针对各个分区所建的索引。在本例中可以看出和局部索引相比全局索引好比一个大索引而局部索引好比13个小索引。”

4.2.5 分区表之相关陷阱

1.索引缘何频频失效

其中最容易出问题的当属分区表的不当操作导致分区索引失效这些操作就是前面讲解分区操作那一部分描述的系列动作这些动作全部都会导致分区索引中的全局索引失效。

 观察全局索引和局部索引的状态

SQL> 
SQL> select index_name,status
  2    from user_indexes
  3  where index_name in ('IDX_PART_TAB_DATE','IDX_PART_TAB_AREA');

INDEX_NAME                     STATUS
------------------------------ ------------------------
IDX_PART_TAB_AREA              N/A
IDX_PART_TAB_DATE              VALID

Elapsed: 00:00:00.00
SQL>

SQL> 
SQL> select index_name,partition_name,status
  2    from user_ind_partitions
  3  where index_name='IDX_PART_TAB_AREA';

INDEX_NAME                     PARTITION_NAME       STATUS
------------------------------ -------------------- ------------------------
IDX_PART_TAB_AREA              P1                   USABLE
IDX_PART_TAB_AREA              P10                  USABLE
IDX_PART_TAB_AREA              P11                  USABLE
IDX_PART_TAB_AREA              P12                  USABLE
IDX_PART_TAB_AREA              P2                   USABLE
IDX_PART_TAB_AREA              P3                   USABLE
IDX_PART_TAB_AREA              P4                   USABLE
IDX_PART_TAB_AREA              P5                   USABLE
IDX_PART_TAB_AREA              P6                   USABLE
IDX_PART_TAB_AREA              P7                   USABLE
IDX_PART_TAB_AREA              P8                   USABLE
IDX_PART_TAB_AREA              P9                   USABLE
IDX_PART_TAB_AREA              P_MAX                USABLE

13 rows selected.

Elapsed: 00:00:00.10
SQL> 

 现在来看索引的状态都非常正常我们先来进行一个分区表的分区truncate操作看看索引是否会失效

SQL> 
SQL> 
SQL> select count(*) from range_part_tab partition(p1);

  COUNT(*)
----------
      6906

Elapsed: 00:00:00.06
SQL> alter table range_part_tab truncate partition p1;

Table truncated.

Elapsed: 00:00:00.19
SQL> 
SQL> select count(*) from range_part_tab partition(p1);

  COUNT(*)
----------
         0

Elapsed: 00:00:00.04
SQL> select index_name,status
  2    from user_indexes
  3  where index_name in ('IDX_PART_TAB_DATE','IDX_PART_TAB_AREA');

INDEX_NAME                     STATUS
------------------------------ ------------------------
IDX_PART_TAB_AREA              N/A
IDX_PART_TAB_DATE              UNUSABLE

Elapsed: 00:00:00.02
SQL> select index_name,partition_name,status
  2    from user_ind_partitions
  3  where index_name='IDX_PART_TAB_AREA';

INDEX_NAME                     PARTITION_NAME       STATUS
------------------------------ -------------------- ------------------------
IDX_PART_TAB_AREA              P1                   USABLE
IDX_PART_TAB_AREA              P10                  USABLE
IDX_PART_TAB_AREA              P11                  USABLE
IDX_PART_TAB_AREA              P12                  USABLE
IDX_PART_TAB_AREA              P2                   USABLE
IDX_PART_TAB_AREA              P3                   USABLE
IDX_PART_TAB_AREA              P4                   USABLE
IDX_PART_TAB_AREA              P5                   USABLE
IDX_PART_TAB_AREA              P6                   USABLE
IDX_PART_TAB_AREA              P7                   USABLE
IDX_PART_TAB_AREA              P8                   USABLE
IDX_PART_TAB_AREA              P9                   USABLE
IDX_PART_TAB_AREA              P_MAX                USABLE

13 rows selected.

Elapsed: 00:00:00.00
SQL> 

可以发现全局索引失效了而局部索引没有失效。

对失效的全局索引进行重建

SQL> 
SQL> alter index IDX_PART_TAB_DATE rebuild;

Index altered.

Elapsed: 00:00:00.10
SQL>
SQL> 
SQL> select index_name,status
  2    from user_indexes
  3  where index_name in ('IDX_PART_TAB_DATE');

INDEX_NAME                     STATUS
------------------------------ ------------------------
IDX_PART_TAB_DATE              VALID

Elapsed: 00:00:00.02
SQL> 

其实分区表的分区操作对局部索引一般都没有影响但是对全局索引影响比较大。Oracle在提供这些分区操作时提供了一个很有用的参数update global indexes可以有效地避免全局索引失效。

update global indexes关键字可避免全局索引失效

SQL> 
SQL> 
SQL> select count(*) from range_part_tab partition(p2);

  COUNT(*)
----------
      7652

Elapsed: 00:00:00.01
SQL> alter table range_part_tab truncate partition p2 update global indexes;

Table truncated.

Elapsed: 00:00:00.10
SQL> select count(*) from range_part_tab partition(p2);

  COUNT(*)
----------
         0

Elapsed: 00:00:00.00
SQL> select index_name,status
  2    from user_indexes
  3  where index_name in ('IDX_PART_TAB_DATE');

INDEX_NAME                     STATUS
------------------------------ ------------------------
IDX_PART_TAB_DATE              VALID

Elapsed: 00:00:00.00
SQL> 

其他分区操作比如分区转移、切割、合并、增删等也和这个分区 truncate 是类似的都允许增加update global indexes关键字从而避免全局索引失效这里就不一一罗列了。

2.有索引效率反而更低

应用分区表的局部索引产生的逻辑读很大。

SQL> 
SQL> show user;
USER is "MAXWELLPAN"
SQL> create index idx_range_list_tab_date on range_list_part_tab(id) local;

Index created.

Elapsed: 00:00:00.41
SQL> set autotrace traceonly
SQL> set linesize 1000
SQL> select *
  2    from range_list_part_tab
  3  where id=100000;

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 1576670538

---------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |                         |     1 |   418 |   132   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE ALL                        |                         |     1 |   418 |   132   (0)| 00:00:01 |     1 |    13 |
|   2 |   PARTITION LIST ALL                        |                         |     1 |   418 |   132   (0)| 00:00:01 |     1 |    10 |
|   3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| RANGE_LIST_PART_TAB     |     1 |   418 |   132   (0)| 00:00:01 |     1 |   130 |
|*  4 |     INDEX RANGE SCAN                        | IDX_RANGE_LIST_TAB_DATE |     1 |       |   131   (0)| 00:00:01 |     1 |   130 |
---------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("ID"=100000)


Statistics
----------------------------------------------------------
         10  recursive calls
          0  db block gets
         37  consistent gets
         11  physical reads
          0  redo size
       1201  bytes sent via SQL*Net to client
        413  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> 

接下来是普通表的查询情况

应用普通表的普通索引产生的逻辑读很小

SQL> 
SQL> create index idx_norm_tab_date on norm_tab(id);

Index created.

Elapsed: 00:00:00.12
SQL> set autotrace traceonly
SQL> set linesize 1000
SQL> select *
  2    from norm_tab
  3  where id=100000;

Elapsed: 00:00:00.06

Execution Plan
----------------------------------------------------------
Plan hash value: 3445789223

---------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                   |     1 |   418 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| NORM_TAB          |     1 |   418 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_NORM_TAB_DATE |     1 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID"=100000)


Statistics
----------------------------------------------------------
          7  recursive calls
          0  db block gets
         21  consistent gets
          1  physical reads
          0  redo size
       1207  bytes sent via SQL*Net to client
        402  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL> 

这个问题其实涉及索引的一个特点就是索引的高度一般比较低理解了这个道理你们就能明白为什么性能差别会如此之大了。

3.无法应用分区条件

分区表设计要考虑在语句中有效用到分区条件有无分区条件差别巨大

SQL> 
SQL> 
SQL> select *
  2    from range_list_part_tab
  3  where id=100000
  4  and deal_date >= sysdate-1
  5  and area_code=591;

no rows selected

Elapsed: 00:00:00.05

Execution Plan
----------------------------------------------------------
Plan hash value: 644683147

---------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                   | Name                    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                            |                         |     1 |   418 |    14   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE ITERATOR                   |                         |     1 |   418 |    14   (0)| 00:00:01 |   KEY |    13 |
|   2 |   PARTITION LIST SINGLE                     |                         |     1 |   418 |    14   (0)| 00:00:01 |   KEY |   KEY |
|*  3 |    TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| RANGE_LIST_PART_TAB     |     1 |   418 |    14   (0)| 00:00:01 |   KEY |   KEY |
|*  4 |     INDEX RANGE SCAN                        | IDX_RANGE_LIST_TAB_DATE |     1 |       |    13   (0)| 00:00:01 |   KEY |   KEY |
---------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter("DEAL_DATE">=SYSDATE@!-1)
   4 - access("ID"=100000)


Statistics
----------------------------------------------------------
        111  recursive calls
          5  db block gets
        112  consistent gets
          7  physical reads
        884  redo size
        582  bytes sent via SQL*Net to client
        668  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
         10  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL> 

4.2.6 有趣的索引组织表

大家还记得之前说过普通堆表操作的不足之处吗比如select * from t where id=1 之类的查询id 列有索引如果是普通的表需要先从索引中获取 rowid然后定位到表中获取 id以外的其他列的动作这就是回表。

分别建索引组织表和普通表进行试验

 

 分别比较索引组织表和普通表的查询性能

 

 4.2.7 簇表的介绍及应用

 

 簇表和索引组织表一样由于结构的特殊性导致更新操作开销非常大所以也需要谨慎使用.

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: oracle