ASSM通过增加数据的分布的随机性来减少争用。在ASSM下,并发的进程基本都选择不同的块来插入数据行。因此,在我们使用序列或者以日期作为插入顺序的表并在其上面建立索引的话,可能将会导致索引的聚簇因子比较大。
SQL> create tablespace assm datafile '+DG' size 100m autoextend on next 100m maxsize 1g segment space management auto;
表空间已创建。
SQL> create tablespace mssm datafile '+DG' size 100m autoextend on next 100m maxsize 1g segment space management manual;
表空间已创建。
SQL> select tablespace_name,segment_space_management from dba_tablespaces where tablespace_name in ('ASSM','MSSM');
TABLESPACE_NAME SEGMEN
------------------------------ ------
ASSM AUTO
MSSM MANUAL
SQL> create table assm_test(id int,flag char(1)) tablespace assm;
表已创建。
SQL> create table mssm_test(id int,flag char(1)) tablespace mssm;
表已创建。
SQL> create sequence assm_s;
序列已创建。
SQL> create sequence mssm_s;
序列已创建。
首先创建2序列和2个表,一个创建在ASSM管理的表空间下,另一个创建在手工段空间管理的表空间下。
SQL> create or replace procedure load_assm_data(v_flag char) as
2 begin
3 for i in 1 .. 1000 loop
4 insert into assm_test values (assm_s.nextval, v_flag);
5 end loop;
6 commit;
7 end;
8 /
过程已创建。
SQL> create or replace procedure load_mssm_data(v_flag char) as
2 begin
3 for i in 1 .. 1000 loop
4 insert into mssm_test values (mssm_s.nextval, v_flag);
5 end loop;
6 commit;
7 end;
8 /
过程已创建。
分别创建2个过程用来为表ASSM_TEST和MSSM_TEST加载数据。
SQL> declare
2 jobname varchar2(30);
3 BEGIN
4 FOR I IN 0..4 LOOP
5 JOBNAME:=DBMS_SCHEDULER.GENERATE_JOB_NAME;
6 DBMS_SCHEDULER.CREATE_JOB(
7 job_name=>JOBNAME,
8 job_type=>'STORED_PROCEDURE',
9 job_action=>'LOAD_ASSM_DATA',
10 auto_drop=>TRUE,
11 enabled=>FALSE,
12 number_of_arguments=>1);
13 DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOBNAME,1,CHR(65+I));
DBMS_SCHEDULER.ENABLE(JOBNAME);
END LOOP;
END;
/
PL/SQL 过程已成功完成。
SQL> declare
jobname varchar2(30);
BEGIN
FOR I IN 0..4 LOOP
JOBNAME:=DBMS_SCHEDULER.GENERATE_JOB_NAME;
DBMS_SCHEDULER.CREATE_JOB(
job_name=>JOBNAME,
job_type=>'STORED_PROCEDURE',
job_action=>'LOAD_MSSM_DATA',
auto_drop=>TRUE,
enabled=>FALSE,
number_of_arguments=>1);
DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOBNAME,1,CHR(65+I));
DBMS_SCHEDULER.ENABLE(JOBNAME);
END LOOP;
END;
PL/SQL 过程已成功完成。
使用JOB的方式,同时启动5个并发进程,向表assm_test和mssm_test插入数据。
SQL> create index assm_test_idx on assm_test(id);
索引已创建。
SQL> create index mssm_test_idx on mssm_test(id);
索引已创建。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'ASSM_TEST',CASCADE=>TRUE);
PL/SQL 过程已成功完成。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'MSSM_TEST',CASCADE=>TRUE);
PL/SQL 过程已成功完成。
SQL> SELECT TABLE_NAME,BLOCKS,NUM_ROWS FROM USER_TABLES WHERE TABLE_NAME IN ('ASSM_TEST','MSSM_TEST');
TABLE_NAME BLOCKS NUM_ROWS
------------------------------ ---------- ----------
ASSM_TEST 20 5000
MSSM_TEST 12 5000
SQL> SELECT INDEX_NAME,BLEVEL,LEAF_BLOCKS,CLUSTERING_FACTOR FROM USER_INDEXES WHERE TABLE_NAME IN ('ASSM_TEST','MSSM_TEST');
INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
ASSM_TEST_IDX 1 11 963
MSSM_TEST_IDX 1 11 20
很明显,ASSM下的空间管理方式下的聚簇因子远远大于手工段空间管理模式下的。
SQL> SELECT DISTINCT DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) BLOCK# FROM ASSM_TEST;
BLOCK#
----------
134
132
141
133
143
135
131
142
145
已选择9行。
可以看到,表ASSM_TEST只有20个BLOCKS,实际表的数据只占用了9个BLOCK,但是聚簇因子却达到了963。 这就是ASSM在避免了表争用问题带来的一个副作用。
SQL> SELECT BLOCK#, COUNT(*)
2 FROM (SELECT DISTINCT DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) BLOCK#, FLAG
3 FROM ASSM_TEST)
4 GROUP BY BLOCK#;
BLOCK# COUNT(*)
---------- ----------
134 2
132 1
141 1
133 3
143 1
135 4
131 1
142 1
145 1
已选择9行。
可以看到大部分块,被一个进程使用,减少了争用。而在MSSM下,一个块基本被多个进程争用。
SQL> SELECT BLOCK#, COUNT(*)
2 FROM (SELECT DISTINCT DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) BLOCK#, FLAG
3 FROM MSSM_TEST)
4 GROUP BY BLOCK#;
BLOCK# COUNT(*)
---------- ----------
129 1
134 4
132 4
133 4
135 4
136 4
130 5
131 4
已选择8行。
下面看看,ASSM对执行计划的影响:
SQL> SELECT * FROM MSSM_TEST WHERE ID BETWEEN 1 AND 50;
已选择49行。
执行计划
----------------------------------------------------------
Plan hash value: 2228967704
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 294 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| MSSM_TEST | 49 | 294 | 3 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | MSSM_TEST_IDX | 49 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID">=1 AND "ID"<=50)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1385 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
49 rows processed
我们从5000行的数据里选取了50行,选择率大约为1/100。此时索引扫描相对较好。
SQL> SELECT * FROM ASSM_TEST WHERE ID BETWEEN 1 AND 50;
已选择49行。
执行计划
----------------------------------------------------------
Plan hash value: 166377213
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 294 | 5 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| ASSM_TEST | 49 | 294 | 5 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ID"<=50 AND "ID">=1)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
27 consistent gets
0 physical reads
0 redo size
1274 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
49 rows processed
但是在ASSM下,ORACLE却选择了成本较高的全表扫描。这就是ASSM的特性带来的对执行计划的影响。如果使用索引,ORACLE计算出来的成本会比全表扫描高。但实际走索引的效率会更好,从逻辑读取也可以看得出来。
SQL> SELECT /*+INDEX(ASSM_TEST)*/ * FROM ASSM_TEST WHERE ID BETWEEN 1 AND 50;
已选择49行。
执行计划
----------------------------------------------------------
Plan hash value: 3633427144
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 294 | 12 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| ASSM_TEST | 49 | 294 | 12 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | ASSM_TEST_IDX | 49 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID">=1 AND "ID"<=50)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1385 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
49 rows processed
SQL> CREATE TABLE ASSM_TEST_2 TABLESPACE ASSM AS SELECT * FROM ASSM_TEST ORDER BY ID;
表已创建。
SQL> CREATE INDEX ASSM_TEST_2_IDX ON ASSM_TEST_2(ID);
索引已创建。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'ASSM_TEST_2',CASCADE=>TRUE);
PL/SQL 过程已成功完成。
SQL> SELECT TABLE_NAME,BLOCKS,NUM_ROWS FROM USER_TABLES WHERE TABLE_NAME LIKE '%SSM%';
TABLE_NAME BLOCKS NUM_ROWS
------------------------------ ---------- ----------
ASSM_TEST 20 5000
MSSM_TEST 12 5000
ASSM_TEST_2 11 5000
SQL> SELECT INDEX_NAME,BLEVEL,LEAF_BLOCKS,CLUSTERING_FACTOR FROM USER_INDEXES WHERE TABLE_NAME LIKE '%SSM%';
INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
MSSM_TEST_IDX 1 11 20
ASSM_TEST_2_IDX 1 11 8
ASSM_TEST_IDX 1 11 963
SQL> set autotrace trace;
SQL> SELECT * FROM ASSM_TEST_2 WHERE ID BETWEEN 1 AND 50;
已选择49行。
执行计划
----------------------------------------------------------
Plan hash value: 1713382986
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 294 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| ASSM_TEST_2 | 49 | 294 | 3 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | ASSM_TEST_2_IDX | 49 | | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID">=1 AND "ID"<=50)
统计信息
----------------------------------------------------------
1 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1385 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
49 rows processed
我们复制了一个与ASSM_TEST一模一样的表。可以看到,重新组织表后,使ORACLE从全表扫描走了索引扫描。
SQL> ALTER SESSION SET SQL_TRACE TRUE;
会话已更改。
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER,'ASSM_TEST',CASCADE=>TRUE);
PL/SQL 过程已成功完成。
SQL> ALTER SESSION SET SQL_TRACE FALSE;
会话已更改。
SQL> select d.value || '/' || lower(rtrim(i.instance, chr(0))) || '_ora_' ||
2 from (select p.spidc' trace_file_name
p.spid || '.trc' trace_file_name
3 from (select p.spid
4 from v$mystat m, v$session s, v$process p
5 where m.statistic# = 1
6 and s.sid = m.sid
7 and p.addr = s.paddr) p,
8 (select t.instance
9 from v$thread t, v$parameter v
10 where v.name = 'thread'
11 and (v.value = 0 or t.thread# = to_number(v.value))) i,
12 (select value from v$parameter where name = 'user_dump_dest') d;
TRACE_FILE_NAME
--------------------------------------------------------------------------------
/u01/app/diag/rdbms/orcl/orcl/trace/orcl_ora_5714.trc
SQL> exit
从 Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, Automatic Storage Management, OLAP, Data Mining
and Real Application Testing options 断开
[oracle@linux ~]$ su - root
口令:
[root@linux ~]# cd /u01/event/
[root@linux event]# cp /u01/app/diag/rdbms/orcl/orcl/trace/orcl_ora_5714.trc .
[root@linux event]# tkprof orcl_ora_5714.trc orcl_ora_5714.txt
TKPROF: Release 11.2.0.1.0 - Development on 星期二 2月 5 21:35:29 2013
Copyright (c) 1982, 2009, Oracle and/or its affiliates. All rights reserved.
启用SQL跟踪,格式化跟踪文件,并在跟踪文件中找到如下SQL语句。
select /*+ no_parallel_index(t, "ASSM_TEST_IDX") dbms_stats
cursor_sharing_exact use_weak_name_resl dynamic_sampling(0) no_monitoring
no_substrb_pad no_expand index(t,"ASSM_TEST_IDX") */ count(*) as nrw,
count(distinct sys_op_lbid(102382,'L',t.rowid)) as nlb,null as ndk,
sys_op_countchg(substrb(t.rowid,1,15),1) as clf
from
"U1"."ASSM_TEST" t where "ID" is not null
其中,NRW=USER_INDEXES.NUM_ROWS,
NLB=USER_INDEXES.LEAF_BLOCKS,
NDK=USER_INDEXES.NUM_DISTINCT,
CLF=USER_INDEXES.CLUSTERING_FACTOR。
修改 sys_op_countchg(substrb(t.rowid,1,15),1) as clf 第二个参数为5(并发的进程数),并在SQLPLUS执行,将得到的CLF值写回到数据字典中。。
SQL> select /*+ no_parallel_index(t, "ASSM_TEST_IDX") dbms_stats
2 cursor_sharing_exact use_weak_name_resl dynamic_sampling(0) no_monitoring
3 no_substrb_pad no_expand index(t,"ASSM_TEST_IDX") */
4 count(*) as nrw,
5 count(distinct sys_op_lbid(102382, 'L', t.rowid)) as nlb,
6 null as ndk,
7 sys_op_countchg(substrb(t.rowid, 1, 15), 5) as clf
from "U1"."ASSM_TEST" t
9 where "ID" is not null;
NRW NLB N CLF
---------- ---------- - ----------
5000 11 9
SQL> SELECT INDEX_NAME,BLEVEL,LEAF_BLOCKS,CLUSTERING_FACTOR FROM USER_INDEXES WHERE TABLE_NAME LIKE '%SSM%';
INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
MSSM_TEST_IDX 1 11 20
ASSM_TEST_2_IDX 1 11 8
ASSM_TEST_IDX 1 11 963
SQL> exec dbms_stats.set_index_stats(user,'ASSM_TEST_IDX',CLSTFCT=>9);
PL/SQL 过程已成功完成。
SQL> SELECT INDEX_NAME,BLEVEL,LEAF_BLOCKS,CLUSTERING_FACTOR FROM USER_INDEXES WHERE TABLE_NAME LIKE '%SSM%';
INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
------------------------------ ---------- ----------- -----------------
MSSM_TEST_IDX 1 11 20
ASSM_TEST_2_IDX 1 11 8
ASSM_TEST_IDX 1 11 9
SQL> set autotrace trace;
SQL> SELECT * FROM ASSM_TEST WHERE ID BETWEEN 1 AND 50;
已选择49行。
执行计划
----------------------------------------------------------
Plan hash value: 3633427144
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 49 | 294 | 3 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| ASSM_TEST | 49 | 294 | 3 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | ASSM_TEST_IDX | 49 | | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID">=1 AND "ID"<=50)
统计信息
----------------------------------------------------------
1 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1385 bytes sent via SQL*Net to client
448 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
49 rows processed
此时,ORACLE也是从全表扫描转为索引扫描。
当使用当oracle使用MSSM时,freelists > 1:sys_op_countchg函数第二个参数就等于freelists。freelist_groups > 1:sys_op_countchg函数第二个参数就等于freelists * freelist_groups
freelists和freelist_groups可以用这个sql查到: select t.freelists,t.freelist_groups,t.* from user_tables t;
当oracle使用ASSM时,它可以同时分配16个新块并进行格式化。这就意味着新的数据将粗略的散布在这16块中,而不是紧密的聚合在一起。
调用sys_op_countchg()函数并将起参数设定为16,这样已经足以产生一个合理的clustering_factor,以取代当前这个毫无意义的值。但是参数16应该作为上限。如果并发进程的数目一般小于16,那么就应该使用并发进程真正的数目作为参数。
反转键索引、为索引添加列和对索引中的列进行重新排序等。函数sys_op_countchg()对这些问题没有帮助。可以通过创建一个只包括驱动列的索引,并计算该索引的clustering_factor,最后将其值传递给原来的索引。
SQL> select sys_op_countchg(substrb(t.rowid,1,15),5) as clf from ASSM_TEST t where "ID" is not null;
CLF
----------
9
SQL> SQL> select sys_op_countchg(substrb(t.rowid,1,15),9) as clf from ASSM_TEST t where "ID" is not null;
CLF
----------
9
SQL> SQL> select sys_op_countchg(substrb(t.rowid,1,15),16) as clf from ASSM_TEST t where "ID" is not null;
CLF
----------
9
在前面的例子中,我设置ASSM_TEST表的并发为5,计算出的clustering_factor为9;通过前面我还可以知道ASSM_TEST在数据主要分布在9个块中国,计算出的clustering_factor还是为9;如果设置并发上线16,计算出的clustering_factor还是为9。在实际工作中,我们不好估算并发的准确数字,就直接传递参数为16也未尝不是一个办法。