为什么忘记commit也会造成select查询的性能问题

今天遇到一个很有意思的问题,一个开发人员反馈在测试服务器ORACLE数据库执行的一条简单SQL语句非常缓慢,他写的一个SQL没有返回任何数据,但是耗费了几分钟的时间。让我检查分析一下原因,分析解决过后,发现事情的真相有点让人哭笑不得,但是也是非常有意思的。我们先简单构造一下类似的案例,当然只是简单模拟。

假设一个同事A,创建了一个表并初始化了数据(实际环境数据量较大,有1G多的数据),但是他忘记提交了。我们简单模拟如下:

  1. SQL> create table test_uncommit
  1.   2  as
  1.   3  select * from dba_objects where 1=0;
  1.  
  1. Table created.
  1.  
  1. SQL> declare rowIndex number;
  1.   2  begin
  1.   3     for rowIndex in 1..70 loop
  1.   4     insert into test_uncommit
  1.   5     select * from dba_objects;
  1.   6     end loop;
  1.   7  end;
  1.   8  /
  1.  
  1. PL/SQL procedure successfully completed.
  1.  
  1. SQL>

另外一个同事B对这个表做一些简单查询操作,但是他不知道同事A的没有提交INSERT语句,如下所示,查询时间用了大概5秒多(这个因为构造的数据量不是非常大的缘故。实际场景耗费了几分钟)

  1. SQL> SET TIMING ON;
  1. SQL> SET AUTOTRACE ON;
  1. SQL> SELECT COUNT(1) FROM SYS.TEST_UNCOMMIT WHERE OBJECT_ID=39;
  1.  
  1.   COUNT(1)
  1. ----------
  1.          0
  1.  
  1. Elapsed: 00:00:05.38
  1.  
  1. Execution Plan
  1. ----------------------------------------------------------
  1. Plan hash value: 970680813
  1.  
  1. ------------------------------------------------------------------------------------
  1. | Id  | Operation          | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
  1. ------------------------------------------------------------------------------------
  1. |   0 | SELECT STATEMENT   |               |     1 |    13 |  6931   (3)| 00:00:10 |
  1. |   1 |  SORT AGGREGATE    |               |     1 |    13 |            |          |
  1. |*  2 |   TABLE ACCESS FULL| TEST_UNCOMMIT |     1 |    13 |  6931   (3)| 00:00:10 |
  1. ------------------------------------------------------------------------------------
  1. Predicate Information (identified by operation id):
  1. ---------------------------------------------------
  1.    2 - filter("OBJECT_ID"=39)
  1.  
  1. Note
  1. -----
  1.    - dynamic sampling used for this statement
  1.  
  1.  
  1. Statistics
  1. ----------------------------------------------------------
  1.           4  recursive calls
  1.           0  db block gets
  1.      229304  consistent gets
  1.       61611  physical reads
  1.     3806792  redo size
  1.         514  bytes sent via SQL*Net to client
  1.         492  bytes received via SQL*Net from client
  1.           2  SQL*Net roundtrips to/from client
  1.           0  sorts (memory)
  1.           0  sorts (disk)
  1.           1  rows processed
  1.  
  1. SQL>

当时是在SQL Developer工具里面分析SQL的执行计划,并没有注意到redo size非常大的情况。刚开始怀疑是统计信息不准确导致,手工收集了一下该表的统计信息,执行的时间和执行计划依然如此,没有任何变化。 如果我们使用SQL*Plus,查看执行计划,就会看到redo size异常大,你就会有所察觉(见后面分析)

  1. SQL> exec dbms_stats.gather_table_stats('SYS','TEST_UNCOMMIT');
  1.  
  1. PL/SQL procedure successfully completed.
  1.  
  1. Elapsed: 00:00:12.29

因为ORACLE里面的写不阻塞读,所以不可能是因为SQL阻塞的缘故,然后我想查看这个表到底有多少记录,结果亮瞎了我的眼睛,记录数为0,但是空间用掉了852 个数据块

  1. SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS FROM DBA_TABLES WHERE TABLE_NAME='TEST_UNCOMMIT';
  1.  
  1. TABLE_NAME                       NUM_ROWS     BLOCKS
  1. ------------------------------ ---------- ----------
  1. TEST_UNCOMMIT                           0        852
  1.  
  1. SQL>

于是我使用Tom大师的show_space脚本检查、确认该表的空间使用情况,如下所示,该表确实使用852个数据块。

  1. SQL> set serverout on;
  1. SQL> exec show_space('TEST_UNCOMMIT');
  1. Free Blocks.............................             852
  1. Total Blocks............................             896
  1. Total Bytes.............................       7,340,032
  1. Total MBytes............................               7
  1. Unused Blocks...........................              43
  1. Unused Bytes............................         352,256
  1. Last Used Ext FileId....................               1
  1. Last Used Ext BlockId...................          88,201
  1. Last Used Block.........................              85
  1.  
  1. PL/SQL procedure successfully completed.
  1.  
  1. SQL>

分析到这里,那么肯定是遇到了插入数据操作,却没有提交的缘故。用下面脚本检查发现一个会话ID为883的对这个表有一个ROW级排他锁,而且会话还有一个事务排他锁,那么可以肯定这个会话执行了DML操作,但是没有提交。

  1. SET linesize 190
  1. COL osuser format a15
  1. COL username format a20 wrap
  1. COL object_name format a20 wrap
  1. COL terminal format a25 wrap
  1. COL req_mode format a20
  1. SELECT B.SID,
  1.        C.USERNAME,
  1.        C.OSUSER,
  1.        C.TERMINAL,
  1.        DECODE(B.ID2, 0, A.OBJECT_NAME,
  1.                      'TRANS-' 
  1.                      ||TO_CHAR(B.ID1)) OBJECT_NAME,
  1.        B.TYPE,
  1.        DECODE(B.LMODE, 0, 'WAITING',
  1.                        1, 'NULL',
  1.                        2, 'Row-S(SS)',
  1.                        3, 'ROW-X(SX)',
  1.                        4, 'SHARE',
  1.                        5, 'S/ROW-X(SSX)',
  1.                        6, 'EXCLUSIVE',
  1.                        ' OTHER')       "LOCK MODE",
  1.        DECODE(B.REQUEST, 0, '',
  1.                          1, 'NULL',
  1.                          2, 'Row-S(SS)',
  1.                          3, 'ROW-X(SX)',
  1.                          4, 'SHARE',
  1.                          5, 'S/ROW-X(SSX)',
  1.                          6, 'EXCLUSIVE',
  1.                          'OTHER')      "REQ_MODE"
  1. FROM   DBA_OBJECTS A,
  1.        V$LOCK B,
  1.        V$SESSION C
  1. WHERE  A.OBJECT_ID(+) = B.ID1
  1.        AND B.SID = C.SID
  1.        AND C.USERNAME IS NOT NULL 
  1. ORDER  BY B.SID,
  1.           B.ID2;

我们在会话里面提交后,然后重新执行这个SQL,你会发现执行计划里面redo size为0,这是因为redo size表示DML生成的redo log的大小,其实从上面的执行计划分析redo size异常,就应该了解到一个七七八八了,因为一个正常的SELECT查询是不会在redo log里面生成相关信息的。那么肯定是遇到了DML操作,但是没有提交。

分析到这里,我们已经知道事情的前因后果了,解决也很容易,找到那个会话的信息,然后定位到哪个同事,让其提交即可解决。但是,为什么没有提交与提交过后的差距那么大呢?是什么原因呢? 我们可以在这个案例,提交前与提交后跟踪执行的SQL语句,如下所示。

  1. SQL> ALTER SESSION SET SQL_TRACE=TRUE;
  1.  
  1. Session altered.
  1.  
  1. SQL> SELECT COUNT(1) FROM SYS.TEST_UNCOMMIT WHERE OBJECT_ID=39;
  1.  
  1.   COUNT(1)
  1. ----------
  1.          0
  1. SQL>
  1.  
  1. SQL> ALTER SESSION SET SQL_TRACE=FALSE;
  1.  
  1. Session altered.

提交前上面SQL生成的跟踪文件为scm2_ora_8444.trc,我们使用TKPROF格式化如下: tkprof scm2_ora_8444.trc out_uncommit.txt 如下所示

提交后,在另外一个会话执行上面的SQL,然后格式化跟踪文件如下所示:

我们发现提交前与提交后两者的物理读、一致性读有较大差别(尤其是一致性读相差3倍多)。这个主要是因为ORACLE的一致性读需要构造cr块,产生了大量的逻辑读的缘故。相关理论与概念如下:

为什么要一致性读,为了保持数据的一致性。如果一个事务需要修改数据块中数据,会先在回滚段中保存一份修改前数据和SCN的数据块,然后再更新Buffer Cache中的数据块的数据及其SCN,并标识其为“脏”数据。

当其他进程读取数据块时,会先比较数据块上的SCN和进程自己的SCN。如果数据块上的SCN小于等于进程本身的SCN,则直接读取数据块上的数据;

如果数据块上的SCN大于进程本身的SCN,则会从回滚段中找出修改前的数据块读取数据。通常,普通查询都是一致性读。

一致性读什么时候需要cr块呢,那就是select语句在发现所查询的时间点对应的scn,与数据块当前所的scn不一致的时候。构造cr块的时候,首先去data buffer中去找包含数据库前镜像的undo块,如果有直接取出构建CR块,这时候是逻辑读,产生逻辑IO;但是data buffer将undo信息写出后,就没有需要的undo信息,就会去undo段找所需要的前镜像的undo信息,这时候从磁盘上读出block到buffer中,这时候产生物理读(物理IO)

select查询的性能的更多相关文章

  1. 为什么忘记commit也会造成select查询的性能问题

    今天遇到一个很有意思的问题,一个开发人员反馈在测试服务器ORACLE数据库执行的一条简单SQL语句非常缓慢,他写的一个SQL没有返回任何数据,但是耗费了几分钟的时间.让我检查分析一下原因,分析解决过后 ...

  2. 忘记commit也会造成select查询的性能问题

    今天遇到一个很有意思的问题,一个开发人员反馈在测试服务器ORACLE数据库执行的一条简单SQL语句非常缓慢,他写的一个SQL没有返回任何数据,但是耗费了几分钟的时间.让我检查分析一下原因,分析解决过后 ...

  3. EntityFramework之原始查询及性能优化(六)

    前言 在EF中我们可以通过Linq来操作实体类,但是有些时候我们必须通过原始sql语句或者存储过程来进行查询数据库,所以我们可以通过EF Code First来实现,但是SQL语句和存储过程无法进行映 ...

  4. [NHibernate]N+1 Select查询问题分析

    目录 写在前面 文档与系列文章 N+1 Select查询问题分析 总结 写在前面 在前面的文章(延迟加载,立即加载)中都提到了N+1 Select的问题,总觉得理解的很不到位,也请大家原谅,这也是为什 ...

  5. php+mysql预查询prepare 与普通查询的性能对比

    prepare可以解决大访问量的网站给数据库服务器所带来的负载和开销,本文章通过实例向大家介绍预查询prepare与普通查询的性能对比,需要的朋友可以参考一下. 实例代码如下: <?php cl ...

  6. SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好?

    SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好? 今天遇到某人在我以前写的一篇文章里问到 如果统计信息没来得及更新的话,那岂不是统计出来的数据时错误的 ...

  7. 使用with语句来写一个稍微复杂sql语句,附加和子查询的性能对比

    今天偶尔看到sql中也有with关键字,好歹也写了几年的sql语句,居然第一次接触,无知啊.看了一位博主的文章,自己添加了一些内容,做了简单的总结,这个语句还是第一次见到,学习了.我从简单到复杂地写, ...

  8. 如何查询Oracle性能监控

    1.监控等待事件select event,sum(decode(wait_time,0,0,1)) prev, sum(decode(wait_time,0,1,0)) curr,count(*)fr ...

  9. select查询原理

    原文:select查询原理 我并非专业DBA,但做为B/S架构的开发人员,总是离不开数据库,一般开发员只会应用SQL的四条经典语句:select ,insert,delete,update.但是我从来 ...

随机推荐

  1. 使用Vagrant machine

    使用Vagrant 查看Vagrant状态 vagrant status SSH vagrant ssh 共享文件 在vagrantfile中添加共享文件配置 Vagrant.configure(2) ...

  2. python面向对象【进阶篇】

    静态方法 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量 ...

  3. 2nd day

    <?php //求数组的平均值 $a3 = array( array(11,12, 13), array(21,22,23, 24, 25), array(31,32,33, 35), arra ...

  4. dubbo源码分析三:consumer注册及生成代理对象

    本章我们将分析一下consumer向注册中心注册,并获取服务端相应的信息,根据这些信息生产代理对象的过程和源码. 1.类图 上图展示了部分消费者注册及生成代理对象过程中需要使用到的类和接口,其中: s ...

  5. Lucene多字段搜索

    最近在学习Lucene的过程中遇到了需要多域搜索并排序的问题,在网上找了找,资料不是很多,现在都列出来,又需要的可以自己认真看看,都是从其他网站粘贴过来的,所以比较乱,感谢原创的作者们!     使用 ...

  6. border、margin、padding属性的区别

    可以先看下这个视频教程:http://my.tv.sohu.com/us/97014746/64226777.shtml 本文参考:http://www.cnblogs.com/chinhr/arch ...

  7. Linq to sql并发与事务

    本文转载:http://www.cnblogs.com/lovecherry/archive/2007/08/20/862365.html 检测并发 首先使用下面的SQL语句查询数据库的产品表: se ...

  8. htaccess 正则规则整理(转)

    为了方便 htaccess 编写正则,这里整理了一下 htaccess 的正则规则. # —— 位于行首时表示注释. [F] —— Forbidden(禁止): 命令服务器返回 403 Forbidd ...

  9. Java 8 Features – The ULTIMATE Guide--reference

    Now, it is time to gather all the major Java 8 features under one reference post for your reading pl ...

  10. android ImageView scaleType属性(转)

    使用ImageView时经常会用到scaleType属性,如: 1 2 3 4 5 6 7 8 9 <ImageView   android:layout_width="50dp&qu ...