一、日常工作中出现的“灵异事件”

利用BI工具在进行整体流程跑批的时候,一段时间就会报错:临时表空间不足

生产环境中为了保证系统的稳定,一般不采取重启数据库的方式。所以扩充表空间或者重建临时表空间,就成了首选。

之前一直用ALTER TABLESPACE 临时表空间名 ADD tempFILE '/u01/app/oradata/orcl/XXX.dbf' SIZE 2G AUTOEXTEND on ; 增加临时表空间文件来扩容,从而解决表空间不足的问题。

这样的方式治标不治本,因为隔一段时间就会出现临时表空间不足的情况。当时临时表空间已经扩展到190个G了,但是依然会出现表空间不足的情况。

SELECT ROUND((F.BYTES_FREE  + F.BYTES_USED)/1024/1024/1024, 2)                         AS "TOTAL(GB)",
       ROUND(((F.BYTES_FREE  + F.BYTES_USED) - NVL(P.BYTES_USED, 0))/1024/1024/1024,2) AS "FREE(GB)",
       D.FILE_NAME                                                                     AS "TEMP_FILE",
       ROUND(NVL(P.BYTES_USED, 0)/1024/1024/1024, 2)                                   AS "USED(GB)" ,
       ROUND((F.BYTES_USED + F.BYTES_FREE)/1024/1024/1024, 2)                          AS "TOTAL(GB)",
       ROUND(((F.BYTES_USED + F.BYTES_FREE) - NVL(P.BYTES_USED, 0))/1024/1024/1024, 2) AS "FREE(GB)" ,
       ROUND(NVL(P.BYTES_USED, 0)/1024/1024/1024, 2)                                   AS "USED(GB)"
FROM SYS.V_$TEMP_SPACE_HEADER F ,DBA_TEMP_FILES D ,SYS.V_$TEMP_EXTENT_POOL P
WHERE F.TABLESPACE_NAME(+) = D.TABLESPACE_NAME
  AND F.FILE_ID(+) = D.FILE_ID
  AND P.FILE_ID(+) = D.FILE_ID;

二、问题溯源

通过上面的SQL查询临时表空间文件,发现一共七个扩容文件,全部被占用,使用率近乎100%

一般来说程序在进行排序、创建或者重建索引、distinct、group by的时候都要使用表空间,但是程序运行结束之后,会释放掉这些资源,标记为free状态。

所以需要寻找到底是哪个SQL一直使用临时表空间,而且没有释放。在查看之前,先确认当前系统是否还有程序在跑批,排除这些意外因素。

SELECT se.username,
       se.sid,
       se.serial#,
       se.SQL_ID,
       se.sql_address,
       se.machine,
       sa.SQL_TEXT,
       se.program,
       su.tablespace,
       su.segtype,
       su.contents
  FROM v$session se,
       v$sort_usage su,
       v$sqlarea sa
WHERE se.saddr=su.session_addr  and se.SQL_ID=sa.SQL_ID

通过这段SQL中的SQL_TEXT字段可以看到占用临时表空间的执行SQL.全都是同一个SQL,其中用到了with语法,创建了一个临时表,这部分资源,在程序结束之后,一直没有释放掉。

之前也用过with语法,当SQL执行结束之后,临时表空间会被正常释放掉,这里一直被占用,很可能是别的原因。通过查看program字段,我发现这些全都是JDBC thin。

至此问题已经全部整理清楚:这是一个ORCLE的bug(Bug 5723140 – Temp LOB space not released after commit [ID 5723140.8])。
从10.2.0.4开始虽然已经修复了该bug,但是默认情况下,为了更加高效的利用temp,在session未断开前,不自动释放temp 空间。

三、解决方法

找到原因之后就好办了。我当时的应急办法是手工杀掉这些进程

Alter system kill session 'sid,serial#';

等清理结束之后,我需要一个可以让系统自己来关闭这些会话的程序。当时看网上有从java角度,写释放资源的,但是我们当时用的是外包公司的产品,为了这个事情,单独打个补丁,显得有些小题大作。

后来我想用shell来定期处理,先获取会话的sid和serial#,再执行杀掉会话的SQL,这个需要获取oracle的管理员权限,放到生产上的话,风险比较大。

所以最后决定用触发器来解决这个事情,通过logon触发器,在指定用户登录后,对会话设置event 60025事件来达到不断开会话的情况下及时释放临时段。

create or replace trigger kill_with after logon on schema
begin
execute immediate 'alter session set events ''60025 trace name context forever''';
end;

至此,通过jdbc连接oracle而产生的临时表空间占用问题就全部解决了。