今天遇到一个很有意思的问题,一个开发人员反馈在测试服务器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)

为什么忘记commit也会造成select查询的性能问题的更多相关文章

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

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

  2. select查询的性能

    为什么忘记commit也会造成select查询的性能问题 今天遇到一个很有意思的问题,一个开发人员反馈在测试服务器ORACLE数据库执行的一条简单SQL语句非常缓慢,他写的一个SQL没有返回任何数据, ...

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

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

  4. centos LAMP第四部分mysql操作 忘记root密码 skip-innodb 配置慢查询日志 mysql常用操作 mysql常用操作 mysql备份与恢复 第二十二节课

    centos  LAMP第四部分mysql操作  忘记root密码  skip-innodb 配置慢查询日志 mysql常用操作  mysql常用操作 mysql备份与恢复   第二十二节课 mysq ...

  5. 关于SubSonic3.0插件使用SqlQuery或Select查询时产生的System.NullReferenceException异常修复

    早上在编写执行用例时,突然爆异常System.NullReferenceException: 未将对象引用设置到对象的实例 执行代码:

  6. access数据库select查询top时无效的解决办法

    access数据库select查询top时有时无效,原因就是在使用Order by时,且排序的条件中数据有重复的. 比如:select top 10 * from table1 order by cd ...

  7. MySQL之select查询、function函数

    一.select查询 //查询某张表所有数据 select * from temp; //查询指定列和条件的数据 //查询name和age这两列,age等于22的数据 ; //as对列重命名 //as ...

  8. 把一个select查询结果插入到一个表(可选指定字段和值实例)

    把一个select查询结果插入到一个表(可选指定字段和值实例) insert into  bak (cc,yf) select cc,9 from ket insert into bak (cc,yf ...

  9. Sql Server 函数的操作实例!(执行多条语句,返回Select查询后的临时表)

    Sql Server 函数的操作实例!(执行多条语句,返回Select查询后的临时表) SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ==== ...

随机推荐

  1. Oracle Tuning 基础概述01 - Oracle 常见等待事件

    对Oracle数据库整体性能的优化,首先要关注的是在有性能问题时数据库排名前几位等待事件是哪些.Oracle等待事件众多,随着版本的升级,数量还在不断增加,可以通过v$event_name查到当前数据 ...

  2. STL模板中的map的使用与例题

    最近的计分赛,记得自己的都只是过了两题.遇到了两次map,自己在寒假看了一点的map,只知道在字符串匹配的时候可以用的到.但是自己对map的使用还是不够熟练使用,这回在第一次和第二次的计分赛中都遇到可 ...

  3. MVC学习系列11---验证系列之客户端验证

    前面学习了,服务端验证,这篇文章中,我们接着学习客户端验证,客户端的验证,使用Jquery和Jquery插件来实现[jquery.validate.min.js and jquery.validate ...

  4. Js运动框架

    <!DOCTYPE html> <html> <head> <title></title> </head> <body&g ...

  5. SQL 性能优化

    当我看到sql执行很慢的时候就在想为什么这么慢? 不外乎数据大,sql语句复杂,没有索引. 如果要进行优化的话可以从对应的这三个问题出发: 看看表是否可以进行拆分成小表,拆分sql语句,建立适合的索引 ...

  6. JS进阶之非阻塞

    回调函数,阻塞和非阻塞对于初学者来说总是一些不好理解的东西,最好的办法就是通过实际写代码去体会.笔者今天就通过一个例子来简单解释一下JS的非阻塞,分享分享我的理解. 首先回调函数:这是一个异步过程,简 ...

  7. Top 15 不起眼却有大作用的 .NET功能集

    目录 1. ObsoleteAttribute2. 设置默认值属性: DefaultValueAttribute3. DebuggerBrowsableAttribute4. ??运算符5. Curr ...

  8. 从零开始学 Java - 搭建 Spring MVC 框架

    没有什么比一个时代的没落更令人伤感的了 整个社会和人都在追求创新.进步.成长,没有人愿意停步不前,一个个老事物慢慢从我们生活中消失掉真的令人那么伤感么?或者说被取代?我想有些是的,但有些东西其实并不是 ...

  9. 【夯实PHP基础】PHP发送邮件(PHPMailer)

    本文地址 参考地址 分享提纲: 1. 概述 2. 编写代码发送邮件 3. 参考文档 1. 概述 本文是讲利用邮件类库 PHPMailer来发送邮件方法. 我们在做project的时候常常需要邮件的功能 ...

  10. 26、ASP.NET MVC入门到精通——后台管理区域及分离、Js压缩、css、jquery扩展

    本系列目录:ASP.NET MVC4入门到精通系列目录汇总 有好一段时间没更新博文了,最近在忙两件事:1.看书,学习中...2.为公司年会节目做准备,由于许久没有练习双截棍了,难免生疏,所以现在临时抱 ...