补偏救弊 | 关于一致性读与语句性能关系的一大误区

简介:

3c58425b89d7b5bccfd2d18210111565feb37f0a

实际上,我们所说的保证同一时间点一致性读的概念,其背后是物理层面的 block 读,Oracle 会依据你发出 select 命令,记录下那一刻的 SCN 值,然后以这个 SCN 值去同所读的每个 block 上的 SCN 比较,如果读到的块上的 SCN 大于 select 发出时记录的 SCN,则需要利用 Undo 得到该 block 的前镜像,在内存中构造 CR 块(Consistent Read)。

一致性读(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代码

HelloDBA.COM> create table t1 as select * from dba_tables;    

Table created.    

HelloDBA.COM> create table t2 as select * from dba_users;    

Table created.   

HelloDBA.COM> exec dbms_stats.gather_table_stats('DEMO''T1');    

PL/SQL procedure successfully completed.   

HelloDBA.COM> exec dbms_stats.gather_table_stats('DEMO''T2');    

PL/SQL procedure successfully completed.   

HelloDBA.COM> set timing on  

HelloDBA.COM> set autot trace  

HelloDBA.COM> 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代码

Rows (1st) Rows (avgRows (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 将变为165,没错。因为无论该数组大小设为多大,Oracle 总是在第一次读取时读取第一条记录。


SQL代码

HelloDBA.COM> set arraysize 5000  

HelloDBA.COM> set autot trace stat  

HelloDBA.COM> 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  


关于全部扫描的 CG 可以参考该文章了解更多细节:http://www.hellodba.com/reader.php?ID=39&lang=EN

F2 是一张小表,它的全表扫描访问产生的CG为3。

写到这是否可以结束了呢?现在将第二条语句的过滤条件移除看看。


SQL代码

HelloDBA.COM> 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  


仅仅 219 CG?这是一个笛卡尔乘积的关联(无关联条件),怎么会是如此少的 CG 呢?

再次产生 SQL 跟踪文件:

SQL代码

Rows (1st) Rows (avgRows (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代码

HelloDBA.COM> select count(*) from t2;    

  COUNT(*)  

----------  

        54  


但是,笛卡尔乘积不是意味着 m×n 吗?为什么结果是 m+n?

实际上,Oracle 确实对 T1 做了多次重复访问。不过,第一次访问后,读取到的数据被缓存到了私有工作区,接下来的访问就是从私有内存而非共享内存中读取数据。因此,这些访问就没有被记入 CG 当中。

为了获取实际的访问次数,我们使用嵌套关联提示使其从共享内存中读取数据:

SQL代码

HelloDBA.COM> 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 的变化却相当明显。

研究收获

从这个例子中可以注意到两点:

1. 数据获取数组大小会影响 CG;

2. CG 仅包含从共享内存读取的次数;


原文发布时间为:2018-02-28

本文作者:黄玮

本文来自云栖社区合作伙伴“数据和云”,了解相关信息可以关注“数据和云”微信公众号

相关文章
|
7月前
|
安全
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(4)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(4)
|
1月前
|
搜索推荐 测试技术
性能场景之业务模型中二八原则的误区
【2月更文挑战第18天】性能场景之业务模型中二八原则的误区
50 6
性能场景之业务模型中二八原则的误区
|
7月前
|
SQL 移动中间件 网络协议
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(5)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(5)
115 0
|
7月前
|
安全
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(2)
|
7月前
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(3)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(3)
|
7月前
|
缓存 安全 Java
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(1)
带你读《2022技术人的百宝黑皮书》——如何避免写重复代码:善用抽象和组合(1)
|
7月前
|
运维 Java 数据库
如何实现最终一致性,有哪些解决方案
如何实现最终一致性,有哪些解决方案
|
关系型数据库 MySQL 开发者
不可思议的一致性读场景
不可思议的一致性读场景
|
监控 关系型数据库 MySQL
如何避免长事务对业务的影响?
大家好前面我们大概了解了关于事务的隔离级别,实现方式,启动方式的细节,长事务与短事务的区别。今天我们延伸介绍一下,如果因为失误用了长事务,那我们在工作中如何发现问题,排查问题?
|
存储 负载均衡 NoSQL
一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~(下)
阿粉公司有一个 Web 管理系统,使用 Tomcat 进行部署。由于是后台管理系统,所有的网页都需要登录授权之后才能进行相应的操作。 起初这个系统的用的人也不多,为了节省资源,这个系统仅仅只是单机部署。后来随着用的人越来越多,单机已经有点扛不住了,于是阿粉决定再部署了一台机器。
一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~(下)