一致性读(Consistent Gets,CG)是反映SQL语句性能的一项重要数据。它通常作为我们语句调优的指标。一般情况下,通过该数据可以比较两条语句或者同一语句的不同执行计划之间的性能。然而,某些情况下,它并不会完全反映出语句的性能。 我们先看两份性能统计数据:
SQL代码
- SQL 1:
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 460 consistent gets
- 0 physical reads
- 0 redo size
- 1203583 bytes sent via SQL*Net to client
- 3868 bytes received via SQL*Net from client
- 306 SQL*Net roundtrips to/from client
- 0 sorts (memory)
- 0 sorts (disk)
- 4563 rows processed
-
- SQL 2:
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 167 consistent gets
- 0 physical reads
- 0 redo size
- 267325 bytes sent via SQL*Net to client
- 3868 bytes received via SQL*Net from client
- 306 SQL*Net roundtrips to/from client
- 1 sorts (memory)
- 0 sorts (disk)
- 4563 rows processed
可以看到,第一条语句的CG是第二条语句的近3倍,看起来应该是第二条语句的性能更好。是否真是如此? 那再看看这两条语句是如何构造执行的:
SQL代码
- sql> create table t1 as select * from dba_tables;
-
- Table created.
-
- sql> create table t2 as select * from dba_users;
-
- Table created.
-
- sql> exec dbms_stats.gather_table_stats('HXY', 'T1');
-
- PL/SQL procedure successfully completed.
-
- sql> exec dbms_stats.gather_table_stats('HXY', 'T2');
-
- PL/SQL procedure successfully completed.
-
- sql> set timing on
- sql> set autot trace
- sql> select * from t1;
-
- 4563 rows selected.
-
- Elapsed: 00:00:00.10
-
- Execution Plan
-
- Plan hash value: 3617692013
-
-
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-
- | 0 | SELECT STATEMENT | | 4563 | 1078K| 49 (0)| 00:00:01 |
- | 1 | TABLE ACCESS FULL| T1 | 4563 | 1078K| 49 (0)| 00:00:01 |
-
-
-
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 460 consistent gets
- 0 physical reads
- 0 redo size
- 1203583 bytes sent via SQL*Net to client
- 3868 bytes received via SQL*Net from client
- 306 SQL*Net roundtrips to/from client
- 0 sorts (memory)
- 0 sorts (disk)
- 4563 rows processed
-
- HelloDBA.COM> select * from t1, t2 where t2.username='SYS';
-
- 4563 rows selected.
-
- Elapsed: 00:00:00.23
-
- Execution Plan
-
- Plan hash value: 1323614827
-
-
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-
- | 0 | SELECT STATEMENT | | 4563 | 1581K| 52 (0)| 00:00:01 |
- | 1 | MERGE JOIN CARTESIAN| | 4563 | 1581K| 52 (0)| 00:00:01 |
- |* 2 | TABLE ACCESS FULL | T2 | 1 | 113 | 3 (0)| 00:00:01 |
- | 3 | BUFFER SORT | | 4563 | 1078K| 49 (0)| 00:00:01 |
- | 4 | TABLE ACCESS FULL | T1 | 4563 | 1078K| 49 (0)| 00:00:01 |
-
-
- Predicate Information (identified by operation id):
-
-
- 2 - filter("T2"."USERNAME"='SYS')
-
-
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 167 consistent gets
- 0 physical reads
- 0 redo size
- 267325 bytes sent via SQL*Net to client
- 3868 bytes received via SQL*Net from client
- 306 SQL*Net roundtrips to/from client
- 1 sorts (memory)
- 0 sorts (disk)
- 4563 rows processed
这两条语句并不复杂。如果我们忽略性能统计数据,我们很容易就从其语句逻辑结构或者执行计划判断出它们的性能谁优谁劣。 但是为什么第二条语句的CG更少呢? 我们对它们作SQL运行跟踪,再看格式化的跟踪结果: SQL> show parameter user_dump_dest NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ user_dump_dest string /u01/app/diag/rdbms/hxy/hxy/trace SQL> alter session set sql_trace=true; SQL> select * from t1, t2; SQL>select * from t1; SQL> alter session set sql_trace=false;
SQL代码
- Rows (1st) Rows (avg) Rows (max) Row Source Operation
-
- 4563 4563 4563 MERGE JOIN CARTESIAN (cr=167 pr=0 pw=0 time=38433 us cost=52 size=1619865 card=4563)
- 1 1 1 TABLE ACCESS FULL T2 (cr=3 pr=0 pw=0 time=78 us cost=3 size=113 card=1)
- 4563 4563 4563 BUFFER SORT (cr=164 pr=0 pw=0 time=22958 us cost=49 size=1104246 card=4563)
- 4563 4563 4563 TABLE ACCESS FULL T1 (cr=164 pr=0 pw=0 time=11815 us cost=49 size=1104246 card=4563)
这是第二条语句的计划统计数据。显然,它包含两个部分:对T1和T2的全表扫描访问。 在该执行计划当中,T1的全表扫描的CG为164,当时为什么在第一条语句中对其的全部扫描产生的CG为466呢?这是因为数据获取数组大小(fetch array size)设置的影响。在SQLPlus当中,该设置默认值为15,如果我们将其设得足够大,CG将变为,
SQL代码
- sql> set arraysize 5000
- sql> set autot trace stat
- sql> select * from t1;
-
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 165 consistent gets
- 0 physical reads
- 0 redo size
- 1147039 bytes sent via SQL*Net to client
- 524 bytes received via SQL*Net from client
- 2 SQL*Net roundtrips to/from client
- 0 sorts (memory)
- 0 sorts (disk)
- 4563 rows processed
165. 没错。因为无论该数组大小设为多大,Oracle总是在第一次读取时读取第一条记录。关于全部扫描的CG可以参考该文章了解更多细节:SQL> http://www.hellodba.com/reader.php?ID=39&lang=EN F2是一张小表,它的全表扫描访问产生的CG为3。 写到这是否可以结束了呢?现在将第二条语句的过滤条件移除看看,
SQL代码
- sql> select * from t1, t2;
-
- 246402 rows selected.
-
-
- Statistics
-
- 1 recursive calls
- 0 db block gets
- 219 consistent gets
- 0 physical reads
- 0 redo size
- 14113903 bytes sent via SQL*Net to client
- 181209 bytes received via SQL*Net from client
- 16428 SQL*Net roundtrips to/from client
- 1 sorts (memory)
- 0 sorts (disk)
- 246402 rows processed
仅仅219CG?这是一个笛卡尔乘积的关联(无关联条件),怎么会是如此少的CG呢? 再次产生SQL跟踪文件:
SQL代码
- Rows (1st) Rows (avg) Rows (max) Row Source Operation
-
- 246402 246402 246402 MERGE JOIN CARTESIAN (cr=219 pr=0 pw=0 time=957833 us cost=2553 size=87472710 card=246402)
- 54 54 54 TABLE ACCESS FULL T2 (cr=55 pr=0 pw=0 time=728 us cost=3 size=6102 card=54)
- 246402 246402 246402 BUFFER SORT (cr=164 pr=0 pw=0 time=433549 us cost=2550 size=1104246 card=4563)
- 4563 4563 4563 TABLE ACCESS FULL T1 (cr=164 pr=0 pw=0 time=10674 us cost=47 size=1104246 card=4563)
T1的全表扫描的CG并未变化,T2的CG增加为55。55意味着什么?它是T2的数据记录数加一。
SQL代码
- sql> select count(*) from t2;
-
- COUNT(*)
-
- 54
但是,笛卡尔乘积不是意味着m×n吗?为什么结果是m+n? 实际上,Oracle确实对T1做了多次重复访问。不过,第一次访问后,读取到的数据被缓存到了私有工作区,接下来的访问就是从私有内存而非共享内存中读取数据。因此,这些访问就没有被记入CG当中。 为了获取时间的访问次数,我们使用嵌套关联提示使其从共享内存中读取数据:
SQL代码
- sql> select /*+use_nl(t1) leading(t1)*/* from t1, t2;
-
- 246402 rows selected.
-
- Elapsed: 00:00:07.43
-
- Execution Plan
-
- Plan hash value: 787647388
-
-
- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-
- | 0 | SELECT STATEMENT | | 246K| 83M| 5006 (1)| 00:01:01 |
- | 1 | MERGE JOIN CARTESIAN| | 246K| 83M| 5006 (1)| 00:01:01 |
- | 2 | TABLE ACCESS FULL | T1 | 4563 | 1078K| 49 (0)| 00:00:01 |
- | 3 | BUFFER SORT | | 54 | 6102 | 4956 (1)| 00:01:00 |
- | 4 | TABLE ACCESS FULL | T2 | 54 | 6102 | 1 (0)| 00:00:01 |
-
-
-
- Statistics
-
- 0 recursive calls
- 0 db block gets
- 4568 consistent gets
- 0 physical reads
- 0 redo size
- 16632868 bytes sent via SQL*Net to client
- 181209 bytes received via SQL*Net from client
- 16428 SQL*Net roundtrips to/from client
- 1 sorts (memory)
- 0 sorts (disk)
- 246402 rows processed
尽管执行计划没有变化,但是CG的变化却相当明显。 从这个例子中可以注意到两点: 数据获取数组大小会影响CG; CG仅包含从共享内存读取的次数; 测试环境为: Oracle 11.2.0.3 on Oracle Linux 5 64bit |