这个步骤比较简单,查询v$sort_usage就可以了:

  1. (select username,session_addr,sql_id,contents,segtype,blocks*8/1024/1024 gb
  2. from v$sort_usage order by blocks desc)
  3. where rownum<=200;
  4. USERNAME    SESSION_ADDR     SQL_ID        CONTENTS  SEGTYPE            GB
  5. ----------  ---------------- ------------- --------- --------- -----------
  6. XXXX        0700002949BCD8A0 291nk7db4bwdh TEMPORARY SORT      .9677734375
  7. XXXX        070000294BD99628 291nk7db4bwdh TEMPORARY SORT      .9677734375
  8. XXXX        070000294CD10480 291nk7db4bwdh TEMPORARY SORT      .9677734375
  9. XXXX        070000294DD1AC88 291nk7db4bwdh TEMPORARY SORT      .9677734375
  10. XXXX        070000294CD68D70 291nk7db4bwdh TEMPORARY SORT      .9677734375
  11. XXXX        070000294DBDF760 291nk7db4bwdh TEMPORARY SORT      .9677734375
  12. XXXX        070000294EDB5D10 291nk7db4bwdh TEMPORARY SORT      .9677734375
  13. XXXX        070000294FD7D818 291nk7db4bwdh TEMPORARY SORT      .9677734375
  14. ...结果较多,忽略部分输出...

SQL_ID都是一样的,那这个SQL是否有其特殊性呢?SEGTYPE为SORT表明这个临时段是“排序段”,用于SQL排序,大小居然也是一样,会话占用的临时段大小将近1GB,几百个会话加在一起,想不让临时表空间不撑大都难。

看看这个相同的SQL ID代表的SQL是什么:

  1. SQL_FULLTEXT
  2. --------------------------------------------------------------------------------------------------------------
  3. SELECT  A.LLEVEL,  A.LMODE  FROM TABLE_XXX A  WHERE A.SERVICE_NAME = :SERVICE_NAME AND STATE='Y'

很明显,这是一条非常简单的SQL,没有ORDER BY ,也没有GROUP BY、UNION、DISTINCT等需要排序的,TABLE_XXX是一张普通的表,而不是视图。出现了什么问题?会不会是v$sort_usage的 SQL_ID列有错误?我们查看其中一个会话正在执行的SQL:

  1. SID PREV_SQL_ID   SQL_ID
  2. ----------- ------------- -------------
  3. 3163 291nk7db4bwdh

v$sort_usage中看到某个会话当前没有执行任何SQL,v$sort_usage中的SQL_ID是该会话前一条执行的SQL。为什么这里显示的是会话前一条执行的SQL,关于这个问题后面再详述,但至少有一点是可以判断的:如果大量的临时段都是由会话当前正在执行的SQL所产生的,那说明同时有几百个会话在执行需要大量临时空间的SQL,那系统早就崩溃了。所以这些临时表空间的占用不应该是由当前在执行的SQL所产生的,至少大部分不是。

大部分人的一个错误观点是,临时表空间中当前占用的空间是由会话当前正在执行的SQL所产生的。上面的一个简单的分析判断,情况不应该是这样。我们可以基于查询类SQL的执行过程来分析:

  1. 解析SQL语句(Parse),生成一个游标(Open Cursor)。
  2. 执行SQL语句(Execute),严格说就是执行新产生的游标。
  3. 在游标中取数据(Fetch)。
  4. 关闭游标(Close Cursor)。

关键在第3步。大家都知道取数据有一个array size的概念,表示一次从游标中取多少条数据,这是一个循环的过程。如果SQL查询得到的数据有1000条,每次取100条,则需要取10次。对于Fetch Cursor,有两点:

  1. 一个游标,或者说一条SQL语句,并不要求客户端把所有数据取完,只取了一部分数据就关闭游标也是可以的。
  2. 只要还没有关闭游标,数据库就要维护该游标的状态,如果是排序的SQL,也需要维持该SQL已经排好序的数据。

很显然,从上述第2点可以知道,如果一条SQL使用了临时段来排序,在SQL对应的游标没关闭的情况下,Oracle数据库不会去释放临时段,因为对于Oracle数据库来说,它不会知道客户端是否还要继续取游标的数据。

基于这样的分析,我们只需要随便选择一个占用了接近1GB的会话,查询v$open_cursor,查看其打开的游标中是否有大数据量排序的SQL:

  1. SQL> select sql_id,sorts,rows_processed/executions from v$sql
  2. 2  where parsing_schema_name='ACCT' and executions>0 and sorts>0
  3. 3  and sql_id in (select sql_id from v$open_cursor where sid=4505)
  4. 4  order by 3;
  5. SQL_ID              SORTS ROWS_PROCESSED/EXECUTIONS
  6. ------------- ----------- -------------------------
  7. ...省略部分输出结果...
  8. 86vp997jbz7s6       63283                       593
  9. cfpdpb526ad43         592               35859.79899
  10. cfpdpb526ad43         188               55893.61702
  11. cfpdpb526ad43         443                     71000

最后三个游标,实际上都是同一条SQL语句,排序的数据量最大,我们来看看这条SQL是什么:

  1. SQL_FULLTEXT
  2. ---------------------------------------------------------------------------------------------------
  3. select ... from  c, b, a, d, e where ... order by d.billing_cycle_id desc,e.offer_name,a.acc_name

基于为客户保密的原因,SQL做了处理,能知道这条SQL的确是排了序就行,不过在SQL中看不出来的是,这条SQL没有任何实质性的能够过滤大量数据的条件。那么我们count(*)这条SQL语句看看:

  1. --------
  2. 12122698

出来的结果居然有1200多万条数据,一个前台应用,不知道取1200多万条数据干嘛。但是从rows_processed/executions 只有几万的结果来看,应用在取了几万条数据之后,由于某些原因(最大的可能就是不能再处理更多的数据),不再继续取数据,但是游标也一直没有关闭。

比较容易就能进行演示sort by时临时表空间的占用。

  1. 根据dba_objects建一个测试表T1,使其数据量达到2000万行。
  2. select count(*) from t1;
  3. COUNT(*)
  4. -----------
  5. 20171200
  6. 然后将SQL工作区设置为手动模式,设置sort内存大小限制为200M:
  7. alter session set workarea_size_policy=manual;
  8. alter session set sort_area_size=209715200;
  9. 查询得到当前的会话sid:
  10. select sid from v$mystat where rownum< =1;
  11. SID
  12. -----------
  13. 2111
  14. 执行这下面的代码:
  15. declare
  16. 2     v_object_name varchar2(100);
  17. 3     v_dummy varchar2(100);
  18. 4  begin
  19. 5    for rec in (select * from t1 order by object_id,object_name) loop
  20. 6       select object_type into v_dummy from t1 where rownum<=1;
  21. 7       select object_name into v_object_name from dba_objects where object_id=rec.object_id;
  22. 8       dbms_lock.sleep(60*10);
  23. 9       exit;
  24. 10    end loop;
  25. 11  end;
  26. 12  /
  27. 这段代码会打开一个游标,对2000万的数据量进行排序,然后在循环中只取一条数据,然后就进入sleep。在另一个窗口中监控到2111这个会话的event变成了PL/SQL lock timer,就去查询v$sort_usage:
  28. select a.sql_id sort_sql_id,b.sql_id,b.prev_sql_id, contents,segtype,blocks*8/1024/1024 gb
  29. 2  from v$sort_usage a,v$session b
  30. 3  where a.session_addr=b.saddr
  31. 4  and b.sid=2111;
  32. SORT_SQL_ID   SQL_ID        PREV_SQL_ID   CONTENTS  SEGTYPE            GB
  33. ------------- ------------- ------------- --------- --------- -----------
  34. fabh24prgk2sj bhzf316mdc07w fabh24prgk2sj TEMPORARY SORT      1.444824219
  35. 可以看到v$sort_usage中的SQL_ID(即上述结果中SORT_SQL_ID)与v$session中的pre_sql_id一致,这条SQL是:
  36. @sqlbyid fabh24prgk2sj
  37. SQL_FULLTEXT
  38. --------------------------------------------------------
  39. SELECT OBJECT_NAME FROM DBA_OBJECTS WHERE OBJECT_ID=:B1
  40. 而实际上当前正在执行的SQL是:
  41. @sqlbyid bhzf316mdc07w
  42. SQL_FULLTEXT
  43. ---------------------------------------------------------------------------
  44. declare
  45. v_object_name varchar2(100);
  46. v_dummy varchar2(100);
  47. begin
  48. for rec in (select * from t1 order by object_id,object_name) loop
  49. select object_type into v_dummy from t1 where rownum<=1;
  50. select object_name into v_object_name from dba_objects where object_id=rec.object_id;
  51. dbms_lock.sleep(60*10);
  52. exit;
  53. end loop;
  54. end;

问题分析到这里,很明显确认的是,应用存在问题,也许是业务逻辑问题;也许是根据前台选择的条件拼接的SQL,但是没有任何条件时就查询了所有数 据。接下来就是找来开发人员,至于后面的事就跟这个主题没有太大关系。我们可以根据这个案例来进一步展开,去探寻临时表空间的更多知识点。

这里要展开的第1点是,v$sort_usage中的sql_id是不是会话正在执行的SQL,我们去看看视图fixed_View_definition就知道了:

  1. select x$ktsso.inst_id, username, username, ktssoses, ktssosno, prev_sql_addr, prev_hash_value,
  2. prev_sql_id, ktssotsn, decode(ktssocnt, 0, 'PERMANENT', 1, 'TEMPORARY'), decode(ktssosegt, 1,
  3. 'SORT', 2, 'HASH', 3, 'DATA', 4, 'INDEX', 5, 'LOB_DATA', 6, 'LOB_INDEX' , 'UNDEFINED'), ktssofno,
  4. ktssobno, ktssoexts, ktssoblks, ktssorfno from x$ktsso, v$session where ktssoses = v$session.saddr
  5. and ktssosno = v$session.serial#

原来在v$sort_usage的定义中,就明确地说明了SQL_ID列是v$session中的prev_sql_id列,而不是当前的SQL。至于为什么这样定义,老实说,现在还不知道。

不过从11.2.0.2这个版本开始,v$sort_usage的基表x$ktsso中增加了一个字段ktssosqlid,表示该临时段真正关联的SQL,以上述的测试结果为例,查询这个基表的结果如下:

  1. 2  and ktssosno = v$session.serial#
  2. 3  and v$session.sid=2111;
  3. KTSSOSQLID
  4. -------------
  5. 60t6fmjsw6v8y
  6. @sqlbyid 60t6fmjsw6v8y
  7. SQL_FULLTEXT
  8. ---------------------------------------------------------------------------
  9. SELECT * FROM T1 ORDER BY OBJECT_ID,OBJECT_NAME

可以看到的是我们查询到了真正产生临时段的SQL。

一直以来,v$sort_usage中的SQL_ID误导了很多人。所幸的是Oracle从11.2.0.2开始进行了弥补,MOS中有文档:

Bug 17834663 - Include SQL ID for statement that created a temporary segment in GV$SORT_USAGE (文档 ID 17834663.8)
In previous versions, it was not possible to identify the SQL ID
of the statement that created a given temporary segment in
eg. (G)V$SORT_USAGE.

@ Via the fix for bug:8806817 we added the SQL ID to the X$KTSSO
@ table (ktssosqlid), but it was not exposed in the GV$SORT_USAGE
@ view until now.

The SQL ID of the statement is in column SQL_ID_TEMPSEG

Note that this fix cannot be provided as an interim patch.

我们改良一下v$sort_usage,使用如下的查询来代替:

  1. ktssoses "SADDR",
  2. sid,
  3. ktssosno "SERIAL#",
  4. username "USERNAME",
  5. osuser "OSUSER",
  6. ktssosqlid "SQL_ID",
  7. ktssotsn "TABLESPACE",
  8. decode(ktssocnt, 0, 'PERMANENT', 1, 'TEMPORARY') "CONTENTS",
  9. --注意在12c的v$sort_usage定义中TABLESPACE和CONTENTS已经发生变化了。
  10. decode(ktssosegt, 1, 'SORT', 2, 'HASH', 3, 'DATA', 4, 'INDEX',
  11. 5, 'LOB_DATA', 6, 'LOB_INDEX' , 'UNDEFINED') "SEGTYPE",
  12. ktssofno "SEGFILE#",
  13. ktssobno "SEGBLK#",
  14. ktssoexts "EXTENTS",
  15. ktssoblks "BLOCKS",
  16. round(ktssoblks*p.value/1024/1024, 2) "SIZE_MB",
  17. ktssorfno "SEGRFNO#"
  18. from x$ktsso k, v$session s,
  19. (select value from v$parameter where name='db_block_size') p
  20. where ktssoses = s.saddr
  21. and ktssosno = s.serial#;

要展开的第2点是,v$sort_usage中的SEGTYPE列的不同的值各有什么意义:

  1. SORT:SQL排序使用的临时段,包括order by、group by、union、distinct、窗口函数(window function)、建索引等产生的排序。
  2. DATA:临时表(Global Temporary Table)存储数据使有的段。
  3. INDEX:临时表上建的索引使用的段。
  4. HASH:hash算法,如hash连接所使用的临时段。
  5. LOB_DATA和LOB_INDEX:临时LOB使用的临时段。

根据上述的段类型,大体可以分为三类占用:

  1. SQL语句排序、HASH JOIN占用
  2. 临时表占用
  3. 临时LOB对象占用

临时表空间的异常占用,一种缓步增长的,另一种情况:一下撑满的通常是一个极大数据量的排序或极大的索引的创建。缓步增长的情况,跟系统的内存被逐 渐占用类似,存在“泄露”。比如排序的SQL游标没有关闭,比如本文的案例;比如会话级临时表产生了数据后一直没有清除;临时LOB对象没有清理或泄露。 前两种比较好去分析处理,但是临时LOB的泄露问题就复杂很多。

来看一个测试:

  1. SID
  2. -----------
  3. 1773
  4. declare
  5. 2    v_lob clob;
  6. 3  begin
  7. 4    dbms_lob.createtemporary(v_lob,true);
  8. 5    dbms_lob.writeappend(v_lob,1000,lpad('a',1000,'a'));
  9. 6  end;
  10. 7  /

上述的代码执行完之后,在另一个窗口中,我们查询v$sort_usage:

  1. select a.sql_id sort_sql_id,b.sql_id,b.prev_sql_id, contents,segtype,blocks*8/1024/1024 gb
  2. 2  from v$sort_usage a,v$session b
  3. 3  where a.session_addr=b.saddr
  4. 4  and b.sid=1773;
  5. SORT_SQL_ID   SQL_ID        PREV_SQL_ID   CONTENTS  SEGTYPE            GB
  6. ------------- ------------- ------------- --------- --------- -----------
  7. 9babjv8yq8ru3               9babjv8yq8ru3 TEMPORARY LOB_DATA  .0004882813
  8. @sqlbyid 9babjv8yq8ru3
  9. SQL_FULLTEXT
  10. ---------------------------------------------------------------------------
  11. BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

可以看到,这个会话已经产生了类型为LOB_DATA的临时段。虽然SQL代码已经执行完成,会话已经处于空闲状态,但是临时段仍然存在着。

Oracle中的LOB变量,类似于C语句中的指针,或者类似于JAVA代码中的数据库连接Connection,是需要释放的。上述有问题的代 码,缺少了释放LOB的代码:dbms_log.freetemporary(v_lob)。好在对于这种情况,Oracle提供了一个补救措施,就是设 置60025事件可以自动清理掉不活动的LOB,只需要在参数文件中加上event='60025 trace name context forever'。

在Oracle数据库中,xmltype类型内部也实际上是LOB类型,xmltype类型的数据操作可能会产生较多的LOB临时段。lob类型的 字段上的更改操作,比如lob拼接等,同样会产生LOB临时段。如果在v$sort_usage中发现大量的LOB类型的临时段,那么通常是由于代码存在 问题,没有释放LOB,或者是由于Oracle本身的BUG。在MOS上,如果以lob temporary关键字搜索,会发现相当多的关于lob临时段的泄露或临时段没有释放相关的文档。

最后,不管是什么情况导致的临时表空间被过多占用,通常重启应用能够释放掉临时段,因为会话退出后,相对应的临时段就会被释放。看来,“重启”大法在这种情况下就很有用。

temp表空间被过多占用处理方法的更多相关文章

  1. 解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程

    解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程 昨天开发人员跟我说,执行一个sql语句后,大约花了10分钟,好不容易有一个结果,但是报了一个ora-01652错误,查阅 ...

  2. Oracle system表空间满的暂定解决方法

    Oracle system表空间满的暂定解决方法 数据库用的是Oracle Express 10.2版本的.利用Oracle Text做全文检索应用,创建用户yxl时没有初始化默认表空间,在系统开发过 ...

  3. ora-01652无法通过128(在temp表空间中)扩展temp段

    有两种错误:1.数据表空间不足 2.临时表空间不足 有两种原因:一是临时表空间空间太小,二是不能自动扩展. 分析过程:    既然是temp表空间有问题,那当然就要从temp表空间说起啦.首先要说明的 ...

  4. Oracle Temp表空间切换

    来源于:  http://www.2cto.com/database/201507/418564.html 一.TEMP表空间作用 临时表空间主要用途是在数据库进行排序运算.管理索引.访问视图等操作时 ...

  5. Oracle 临时表空间 temp表空间切换

    一.TEMP表空间 临时表空间主要用途是在数据库进行排序运算.管理索引.访问视图等操作时提供临时的运算空间,当运算完成之后系统会自动清理.当oracle里需要用到sort的时候,PGA中sort_ar ...

  6. Oracle Temp 表空间切换

    一.TEMP表空间作用 暂时表空间主要用途是在数据库进行排序运算.管理索引.訪问视图等操作时提供暂时的运算空间,当运算完毕之后系统会自己主动清理.当 oracle 里须要用到 sort 的时候. PG ...

  7. 直接删除undo及temp表空间文件后的数据库恢复一例

    前几天,某用户研发找到我,说他们的研发库坏了,问我能恢复不?我问他们做了什么操作,一个小男孩儿说,看到空间满了,清除了点儿数据,我说是不是连数据库的文件也清除了,他说没有,他清除的是ORACLE_HO ...

  8. oracle表空间不足时的处理方法

    由于数据文件路径下的空间不足或表空间不足时,需要更换或扩展或新增表空间时,以下简单介绍下几种处理方式(数据文件/opt/oracle/oradata/testdb.dbf,原大小为100M) 一.扩大 ...

  9. mysql 表空间及索引的查看方法

        CONCAT : concat() 方法用于连接两个或多个数组.    database : 数据库(11张) 数据库,简单来说是本身可视为电子化的文件柜——存储电子文件的处所,用户可以对文件 ...

随机推荐

  1. CoreJava笔记之JavaBean、静态方法static和final

    记住两句话: 1.属性是静态绑定到变量类型: 2.方法是动态绑定,由最终对象的方法决定 =============================== 关于JavaBean: 1.不是语法规则,是习惯 ...

  2. 案例20-页面使用redis缓存显示类别菜单

    1 准备工作 1  需要导入所需要的jar包. 2 启动windows版本的redis服务端 3 准备JedisUtils工具类的配置文件redis.properties redis.maxIdle= ...

  3. MySql的用户权限

    用户管理 MySQL数据库中的表与其他任何关系表没有区别,都可以通过典型的SQL命令修改其结构和数据.可以使用GRANT和REVOKE命令.通过这些命令,可以创建和禁用用户,可以在线授予和撤回用户访问 ...

  4. UIBezierPath的使用方法

    UIBezierPath的使用方式: 一,直接添加轨迹,然后stroke或者fill UIColor *blue =[UIColor blueColor]; [blue set]; UIBezierP ...

  5. java向上转型的问题

    import java.util.Arrays;import java.util.HashSet;import java.util.Set;class A{ private String s1 = & ...

  6. php发送邮件功能(PHPMailer-master插件)

    当作一个插件使用即可,放到网站根目录,然后调用里面的mail.php 源码

  7. Linux_vim文本编辑器指令整理

    一般指令模式 : 可以移动光标,可以删除字符和删除整列,可以复制粘贴 编辑模式 : 按下"i, I, o, O, a, A, r, R"任意一个字母时进入;按下ESC退出编辑模式 ...

  8. ssm框架文件上传

    有两种方法 导包和上传配置自己搞: 第一种: 上传单个文件: @RequestMapping("/addfile1") public String addfile(@Request ...

  9. What is the relation of theme and it's derived theme.

    You know, a theme can derive from other theme in two ways: xx.xxx implicit way and parent="xxx& ...

  10. 《JavaWeb从入门到改行》那些年一起学习的Servlet

    目录 获取ServletContext : ServletContext接口中的一些方法 application域存取数据功能 代码演示: application域获取项目文件路径 代码演示: API ...