有关全表扫描的讨论
来源:互联网 发布:台式机品牌 知乎 编辑:程序博客网 时间:2024/05/14 05:48
我想这个大家都是知道的,不就是全表扫描嘛,但是我想问一下大家,什么情况下做全表扫描会比较好,它有什么有关系呢?
小表是使用全表扫描比较好,但是怎么来确定全表扫描比较小呢?在oracle的定义是当表中的HWM小于
db_file_multiblock_read_count中的设置值,这才算小表,这种情况下使用全表扫描要比使用索引会更快。
下面我们做个测试(init.ora文件中:optimizer_mode=first_rows,db_file_multiblock_read_count=8,
也就是说我们必须使用CBO的优化器方式),首先我们建立一个demo的表,表中的serv_id为唯一值,建表语句如下:
create table full_table_scan_demo tablespace TS_OB_1M_01 as select * from zk.cm_user where rownum < 11;
第二步、在这个上面建立索引:
create index idx_ftsd_serv_id on full_table_scan_demo (serv_id) tablespace TS_OB_1M_01 pctfree 10;
第三步、我们收集统计值:
exec dbms_stats.gather_table_stats('CUIGB','FULL_TABLE_SCAN_DEMO',cascade=>TRUE);
第四步、我们做个查询语句的执行计划:(使用sqlplus,set autotrace traceonly)
SQL> select * from full_table_scan_demo where serv_id=2000004461;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=2 Bytes=524)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'FULL_TABLE_SCAN_DEMO' (Cost=3 Card=2 Bytes=524)
2 1 INDEX (RANGE SCAN) OF 'IDX_FTSD_SERV_ID' (NON-UNIQUE) (Cost=2 Card=2)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
1552 bytes sent via SQL*Net to client
236 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed
第五步,我再执行一个SQL(没有创建索引的)
SQL> select * from full_table_scan_demo where cust_id=1111195039;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=2 Bytes=524)
1 0 TABLE ACCESS (FULL) OF 'FULL_TABLE_SCAN_DEMO' (Cost=3 Card=2 Bytes=524)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
5 consistent gets
0 physical reads
0 redo size
1641 bytes sent via SQL*Net to client
236 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed
第六步:我们查看user_tables数据字典中的结果:
SQL> select table_name,blocks from user_tables where table_name='FULL_TABLE_SCAN_DEMO';
TABLE_NAME BLOCKS
------------------------------ ----------
FULL_TABLE_SCAN_DEMO 2
我们看到使用索引与不使用索引在2个数据块的情况下是CBO的cost是一样的都是3.
我们再加数据块来看。首先我们使用truncate将原来的数据清除,再插入:
insert into full_table_scan_demo select * from zk.cm_user where rownum < 121;
再做统计值的收集
exec dbms_stats.gather_table_stats('CUIGB','FULL_TABLE_SCAN_DEMO',cascade => TRUE);
select blocks from user_tables where table_name='FULL_TABLE_SCAN_DEMO';
-------
5
我们对下面做执行计划的生成(有索引):
select * from full_table_scan_demo where serv_id=2000004461;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=1 Bytes=288)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'FULL_TABLE_SCAN_DEMO' (Cost=3 Card=1 Bytes=288)
2 1 INDEX (RANGE SCAN) OF 'IDX_FTSD_SERV_ID' (NON-UNIQUE) (Cost=2 Card=1)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1535 bytes sent via SQL*Net to client
236 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
再对没有索引的字段(cust_id)做执行计划
SQL> select * from full_table_scan_demo where cust_id=1111195039;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=1 Bytes=288)
1 0 TABLE ACCESS (FULL) OF 'FULL_TABLE_SCAN_DEMO' (Cost=3 Card=1 Bytes=288)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1624 bytes sent via SQL*Net to client
236 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
我们对比较他们的成本还是一样,也都是3,但这有个不同,这就是使用索引要存取2个数据块,全表扫描的要访问5个数据块,
但我们知道,db_file_multiblock_read_count=8,这样Oracle就会在一个IO操作中完成,所有问题两个块的成本与5个数据块
的成本基本上没有什么差别,所以我们看到cost都为3。
我们再加大数据块的个数,或说提高HWM,我的操作如下面的SQL:
truncate table full_table_scan_demo drop storage;
insert into full_table_scan_demo select * from zk.cm_user where rownum < 181;
select blocks from user_tables where table_name='FULL_TABLE_SCAN_DEMO';
----------
10
这样我们再做有索引的SQL
SQL> select * from full_table_scan_demo where serv_id=2000004461;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=3 Card=1 Bytes=289)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'FULL_TABLE_SCAN_DEMO' (Cost=3 Card=1 Bytes=289)
2 1 INDEX (RANGE SCAN) OF 'IDX_FTSD_SERV_ID' (NON-UNIQUE) (Cost=2 Card=1)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
1535 bytes sent via SQL*Net to client
236 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> select * from full_table_scan_demo where cust_id=1111195039;
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=4 Card=1 Bytes=289)
1 0 TABLE ACCESS (FULL) OF 'FULL_TABLE_SCAN_DEMO' (Cost=4 Card=1 Bytes=289)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
13 consistent gets
0 physical reads
0 redo size
1624 bytes sent via SQL*Net to client
236 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
这样我们看到如果表中的数据大于db_file_multiblock_read_count=8后,没有使用索引的全表扫描,成本已经变为4了,
使用到索引的成本还是3。
通过上面的我们就能看到全表扫描成本要高与索引的扫描,所以我们建议表再小(就是我们通常说的小表)都要使用索引,
但这个是有一个先决条件的,那就是建立索引的那个字段,必须有很高的数据分散性,如果数据值的分散性不好那就不能
使用,下面我们再来说明这个。
我们再将原来表中的数据清除,使用下面的语句:
truncate table full_table_scan_demo drop storage;
insert into full_table_scan_demo select * from zk.cm_user where rownum < 281;
commit;
exec dbms_stats.gather_table_stats('CUIGB','FULL_TABLE_SCAN_DEMO',cascade => TRUE);
select blocks from user_tables where table_name='FULL_TABLE_SCAN_DEMO';
------
15
先看一下表的中的USER_TPYE字段值的情况:
SQL> select USER_TYPE,count(*) from full_table_scan_demo group by user_type;
USER_TYPE COUNT(*)
--------- ----------
0 28
1 28
2 28
3 28
4 28
5 28
6 28
7 28
8 28
9 28
我们看到表中user_type字段是分了10个等分,这个值在实际不可能出现,只是为了做测试我将这个值修改到这样的。
现在我们在使用user_type字段做个索引:语句如下:
create index idx_ftsd_user_type on full_table_scan_demo (user_type) tablespace ts_ob_1m_01;
再做统计值的收集工作:
exec dbms_stats.gather_table_stats('CUIGB','FULL_TABLE_SCAN_DEMO',cascade => TRUE);
我们执行下面SQL,看有什么结果呢?
SQL> select * from full_table_scan_demo where user_type in (1,2);
已选择56行。
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=5 Card=56 Bytes=16240)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'FULL_TABLE_SCAN_DEMO' (Cost=5 Card=56 Bytes=16240)
3 2 INDEX (RANGE SCAN) OF 'IDX_FTSD_USER_TYPE' (NON-UNIQUE) (Cost=2 Card=56)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
11773 bytes sent via SQL*Net to client
257 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
56 rows processed
再与这个字段的全表扫描来比一下,我们使用FULL这个Hint:
SQL> select /*+ full (full_table_scan_demo) */ * from full_table_scan_demo where user_type in (1,2);
已选择56行。
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=5 Card=56 Bytes=16240)
1 0 TABLE ACCESS (FULL) OF 'FULL_TABLE_SCAN_DEMO' (Cost=5 Card=56 Bytes=16240)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
21 consistent gets
0 physical reads
0 redo size
11786 bytes sent via SQL*Net to client
257 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
56 rows processed
从这两看执行我们看到了,他们的成本都一样,都是5,我们也大概可以从实际中总结出来,
Oracle的rows source result set(行源结果集,可以认为是返回记录行数)是占全表的20%左右,全
表扫描与使用索引基本上是一样的。
我们再将这个返回记录数提高的30%的情况,如下面的语句:
SQL> select /*+ full (full_table_scan_demo) */ * from full_table_scan_demo where user_type in (1,2,3);
已选择84行。
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=5 Card=84 Bytes=24360)
1 0 TABLE ACCESS (FULL) OF 'FULL_TABLE_SCAN_DEMO' (Cost=5 Card=84 Bytes=24360)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
23 consistent gets
0 physical reads
0 redo size
17148 bytes sent via SQL*Net to client
270 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
84 rows processed
SQL> select * from full_table_scan_demo where user_type in (1,2,3);
已选择84行。
执行计划
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=7 Card=84 Bytes=24360)
1 0 INLIST ITERATOR
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'FULL_TABLE_SCAN_DEMO' (Cost=7 Card=84 Bytes=24360)
3 2 INDEX (RANGE SCAN) OF 'IDX_FTSD_USER_TYPE' (NON-UNIQUE) (Cost=2 Card=84)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
22 consistent gets
0 physical reads
0 redo size
17143 bytes sent via SQL*Net to client
270 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
84 rows processed
我们看到30%以上使用索引的成本要高于全表扫描的。
下面我们说一下什么时候Oracle会使用全表扫描:
第一、没有或是无效索引。 当使用的SQL语句中没有对就的字段做索引,或是索引字段顺序与where子句中的字段顺序不一致时,当使用非前导字段做谓词条件,还就是在where子句中使用已经有索引字段做函数操作,如:col1字段有一个单索引,在where子句中使用upper(col1)=:b,这都使用不到索引。
第二、大数据量,如果SQL语句将操作表中的绝大多数据块,那么SQL语句将使用全表扫描,既是该字段有索引。
第三、小表,也就是前面我们说到的,当HWM小于db_file_multiblock_read_count值时,会使用全表扫描。
第四、高并行度,当表或SQL指定了一个高的并行运行(parallel degree),Oracle会倾向于使用全表扫描。
第五、使用全表扫描的提示(hint),就面上面我们做的那个例一样。
- 有关全表扫描的讨论
- 有关rest的讨论
- 有关sizeof的讨论
- 有关死锁的讨论
- 有关信号量的讨论
- 全表扫描和索引扫描的区别
- 全表扫描和索引扫描的区别
- 全表扫描下的逻辑读
- 会引起全表扫描的写法
- 导致全表扫描的SQL
- 导致全表扫描的情况
- 造成mysql全表扫描的原因
- Mysql避免全表扫描的方法
- 避免全表扫描的sql优化
- 获得全表扫描的对象
- 有关设计的一场讨论
- 有关随机数的一些讨论
- 有关C++ template的讨论
- 服务级后门自己做
- Procedure to install Oracle 10g
- Solaris commands
- CBitmapButton的使用
- jsp,struts处理中文乱码问题
- 有关全表扫描的讨论
- ddd
- 重拾Java(三)
- 我心目中的女神,,,居然有人叫自己男人一样的神?!
- ACE_TRACE为何不产生日志方法调用?
- 网页屏蔽(左右键,代码等)的非JS方法
- MP4Box使用一二例
- 刚刚修好了我的鼠标,一个字,爽!(06-8-10)
- 关于dxsdk_aug2006.exe(06-8-10)