ORA-01555错误详解

一:‍
‍在电信行业这种数据量巨大的环境中,ora-01555错是一个很常见的错误。这个错误使得应用失败。例如,这一错误可能停止一个在深夜运行的批处理任务,随后也使依赖于该任务的其他任务失败。这使用户不能及时得到所需的信息(如报表没打印出来、数据未被导出等等)。尽管这一错误通常发生在大任务上,但在小任务上也会发生。
  ORA-1555通常是一个偶然出现的错误。有时在发生了该错误以后,重新运行该任务就有可能不再碰到类似的错误。这个错误最麻烦的是它并不会立刻发生,运行时间长的任务在错误失败以前可能已经运行了一段时间了(可能几个小时)。只是简单地重新运行该任务并不能保证它能成功,可能在运行了一段时间以后仍然失败。
  1 原因分析
  ORA-1555错的根本原因是因为Oracle要保证读一致性。读一致性是指当有多个用户对一个数据块内的行进行修改时,这些块变“脏”或处于变化之中直到被确认。在被确认以前,它们对事务中的所有语句都是可见的,但是对别的事务或语句而言是不可见的。一旦确认以后,对所有后继的事务或语句就都是可见的了。但在事务被确认前的语句不能看到修改,因为这些修改还未发生。
  例如,事务T 1(如对某大表的exp操作)在2 2 :0 0开始而事务T 2(如对同一大表的update操作)在2 2 :0 1时开始,因为T 1需遍历一个很大的表,其读取要花很长的时间,而T 2可能对同一个表中的数据进行基于索引的更新操作。这样, T2可能在几秒钟之内完成,而T 1可能要运行很长时间,假定4 0分钟。当T 1到达T 2做过修改的地方时(根据当前的S C N时间戳可以识别出新作的改变),尽管T 2所进行的写已经被确认,但为了保证读一致性,它不会读到修改后的数据,它只访问在2 2 :0 0时的数据,在2 2 :0 1时所做的改变不能被读取 。T 1从回滚段中读取改变前的数据以保证读一致性。但因为事务T2已经提交,T2事务使用的回滚段oracle认为已经可以重新利用,当回滚段太少或事务较密集时,oracle有可能会用新事务覆盖掉原来T2事务的回滚段,这时T1事务读到被T2修改过的数据时,再从回滚段中就无法找到修改前的数据,这时就会报ORA-1555,snapshot too old错。
  下面我们可以结合实例来将此过程回溯一遍:
  (1)事务T1在22点开始执行了对某一个大表Test1的exp操作(Test1表数据量可能有几千万甚至更多),那么按照经验,此操作可能需要执行40分钟左右或更长;
  (2)事务T2在22点01分开始执行对Test1表某行的update操作,并且操作条件上有索引(将col1为00的行,col2值由90修改为100),故此操作很快完成,比如5秒钟完成操作并commit;
  (3)此时事务T2已经执行完毕,而事务T1还在执行中;
  (4)当事务T1需要将col1为00的行导出为dmp文件时,Oracle为了保证读一致性,即T1导出的必须是22点时数据库表的值,故col1为00的行对于T1任务来说值仍然为90,而非100;
  (5)由于T2事务在22点02分前就已经做完(提交),并且T2认为回滚段是可以重新利用的;
  (6)如果此时由于回滚段太少或业务量较密集,oracle就可能会重新利用刚才T2事务所使用的回滚段。这时T1事务读到此处时,就会造成无法找到回滚段中修改前的数据,产生错误。
  2 9i中对回滚段管理
  在9i中,可以有两种解决方法来维护事务的读一致性,即或者使用自Oracle 6以来就一直使用的回滚段,或者是使用Undo Tablespace来进行的自动重做管理,但是这两种方法不能同时使用。
  考试大建议在9i 中使用回滚表空间而不是8i 的回滚段模式来管理数据库。
  (1)建立undotablespace
  建立undotablespace的语法如下:
  create undotablespace tablespace_name
  datafile ’fullpath+datafilename’ size XXM
  [autoextend on|off next XX maxsize XX]; 来
‍二:

写了段java操作数据库的代码

Java代码  String getIPList="select t.dns_ip from t_dnscachetotal t where t.locid=0";         
 String getLocid="select  t3.locid from (select max(t2.ipstart) ipstart,max(t2.ipend) ipend from t_GGMAP_IP t2 where t2.ipstart<=query_ip(?)) t1,t_GGMAP_IP t3 where t1.ipend>=query_ip(?) and t1.ipstart=t3.ipstart";         
String updateDnsCacheTotal="update t_dnscachetotal t set t.locid=? where t.dns_ip=?";             
stmt=con.prepareStatement(getIPList);         
 rs=pstmt.executeQuery();         
String ip;                   
 ResultSet rs2;         
 int countupdate=0;         
while(rs.next()){               
 ip=rs.getString(1);                   
pstmt2 = con.prepareStatement(getLocid);                 
pstmt2.setString(1, ip);                 
pstmt2.setString(2, ip);                 
rs2=pstmt2.executeQuery();               
 int locid=-1;                 
while(rs2.next()){                   
 locid=rs2.getInt(1);                  }
  pstmt2.close();                 
  rs2.close();                 
  countupdate++;                 
  pstmt2=con.prepareStatement(updateDnsCacheTotal);                 
  pstmt2.setInt(1, locid);                 
  pstmt2.setString(2, ip);                 
  pstmt2.executeUpdate();                 
    pstmt2.close();                               
    }         
  pstmt.close();         
  rs.close(); 
String getIPList="select t.dns_ip from t_dnscachetotal t where t.locid=0"; String getLocid="select t3.locid from (select max(t2.ipstart) ipstart,max(t2.ipend) ipend from t_GGMAP_IP t2 where t2.ipstart<=query_ip(?)) t1,t_GGMAP_IP t3 where t1.ipend>=query_ip(?) and t1.ipstart=t3.ipstart"; String updateDnsCacheTotal="update t_dnscachetotal t set t.locid=? where t.dns_ip=?"; pstmt=con.prepareStatement(getIPList); rs=pstmt.executeQuery(); String ip; ResultSet rs2; int countupdate=0; while(rs.next()){ ip=rs.getString(1); pstmt2 = con.prepareStatement(getLocid); pstmt2.setString(1, ip); pstmt2.setString(2, ip); rs2=pstmt2.executeQuery(); int locid=-1; while(rs2.next()){ locid=rs2.getInt(1); } pstmt2.close(); rs2.close(); countupdate++; pstmt2=con.prepareStatement(updateDnsCacheTotal); pstmt2.setInt(1, locid); pstmt2.setString(2, ip); pstmt2.executeUpdate(); pstmt2.close(); } pstmt.close(); rs.close();

 

大体就这样。。我删了一部分代码。

核心的问题就在于

while(rs.next()){
     
    }

因为rs.next实际上是对oracle某表持续的查询,而在循环中又在不断地update这个表,从而导致了这个1555错误,

ora 1555别人的例子 写道首先了解Oracle在什么情况下会产生ORA-01555错误:

假设有一张6000万行数据的testdb表,预计testdb全表扫描1次需要2个小时,参考过程如下:
1、在1点钟,用户A发出了select * from testdb;此时不管将来testdb怎么变化,正确的结果应该是用户A会看到在1点钟这个时刻的内容。
2、在1点30分,用户B执行了update命令,更新了testdb表中的第4100万行的这条记录,这时,用户A的全表扫描还没有到达第4100万条。毫无疑问,这个时候,第4100万行的这条记录是被写入了回滚段,假设是回滚段UNDOTS1,如果用户A的全表扫描到达了第4100万行,是应该会正确的从回滚段UNDOTS1中读取出1点钟时刻的内容的。
3、这时,用户B将他刚才做的操作提交了,但是这时,系统仍然可以给用户A提供正确的数据,因为那第4100万行记录的内容仍然还在回滚段UNDOTS1里,系统可以根据SCN到回滚段里找到正确的数据,但要注意到,这时记录在UNDOTS1里的第4100万行记录已经发生了重大的改变:就是第4100万行在回滚段UNDOTS1里的数据有可能随时被覆盖掉,因为这条记录已经被提交了!
4、由于用户A的查询时间漫长,而业务在一直不断的进行,UNDOTS1回滚段在被多个不同的transaction使用着,这个回滚段里的extent循环到了第4100万行数据所在的extent,由于这条记录已经被标记提交了,所以这个extent是可以被其他transaction覆盖掉的!
5、到了1点45分,用户A的查询终于到了第4100万行,而这时已经出现了第4条说的情况,需要到回滚段UNDOTS1去找数据,但是已经被覆盖掉了,这时就出现了ORA-01555错误。
 

明显我是犯了同样的错误。而解决办法大致有三个

首先:

SQL> show parameter undo
 
NAME                                TYPE        VALUE
------------------------------------ ----------- ------------------------------
undo_management                      string      AUTO
undo_retention                      integer    900
undo_tablespace                      string      UNDOTBS1

 

可以看到我们是自动管理undo的,(11g),另外undo_retention时间是900s,这个是可以考虑放大的,

写道关于初始化参数UNDO_RETENTION的设置,严格说起来也是与UNDO表空间有关系,但是思量再三,我觉着还是有必要单拎出来详细介绍。

该参数用来指定UNDO段中数据保存的最短时间,以秒为单位,是一个动态参数,完全可以在实例运行时随时修改��通常默认是900秒,也就是15分钟。

首先要注意,UNDO_RETENTION只是指定UNDO段中数据的过期时间,并不是说,UNDO段中的数据一定会在UNDO表空间中保存15分钟。如一个新事务开始的时候,如果此时UNDO表空间已经被写满,则新事务的数据会自动覆盖已提交事务的数据,而不管这些数据是否已过期,因此呢,这就又关联回了第一点,当你创建一个自动管理的UNDO表空间时,还要注意其空间大小,要尽可能保证UNDO表空间有足够的存储空间。

同时还要注意,也并不是说,UNDO_RETENTION中指定的时间一过,已经提交事务中的数据就立刻无法访问,当超出UNDO_RETENTION参数指定的时间后,这部分数据占用的空间将会被标识为可重用,不过只要不被别的事务触发的数据覆盖,它会仍然存在,并可以随时被Flashback特性引用。如果你的UNDO表空间足够大,而数据库又不是那么繁忙,那么其实UNDO_RETENTION参数的值并不会影响到你,哪怕你设置成1(这么说好像绝对了点,大家一定要注意理解,别钻牛角尖),只要没有事务去覆盖UNDO数据,这部分数据就会持续有效。因此呢,再次重复那句话,要注意UNDO表空间的大小,保证其有足够的存储空间。

最后,只有在一种情况下,UNDO表空间能够确保UNDO中的数据在UNDO_RETENTION指定时间过期前一定有效,就是为UNDO表空间指定RETENTION GUARANTEE,指定之后,不会覆盖UNDO表空间中未过期的UNDO数据,例如:

SQL> ALTER TABLESPACE UNDOTBS1 RETENTION GUARANTEE; 如果想禁止UNDO表空间RETENTION GUARANTEE,例如:

SQL> ALTER TABLESPACE UNDOTBS1 RETENTION NOGUARANTEE; 转了一圈,问题又回来了,既然它看起来有用又像没有用,为什么还要设置它呢?嘿嘿,就我理解,其存在的真实用途,就是提醒你UNDO表空间很重要,给它指定分配一个合适的大小更重要哟。

 但是从这篇文章中明显发现,undo_retention参数意义很小,而且默认的TABLESPACE UNDOTBS1 一般都是NOGUARANTEE,这个是一个考虑解决1555错误的方法,但是我个人觉得大部分情况绝不是因为undo_retention过小引起的,应该都是因为UNDOTBS1 占满了的缘故,所以

1、扩大回滚段

因为回滚段是循环使用的,如果回滚段足够大,那么那些被提交的数据信息就能保存足够长的时间是那些大事务完成一致性读取。

2、增加undo_retention时间

在undo_retention规定的时间内,任何其他事务都不能覆盖这些数据。

3、优化相关查询语句,减少一致性读。

减少查询语句的一致性读,就降低读取不到回滚段数据的风险。这一点非常重要!

4、减少不必要的事务提交

提交的事务越少,产生的回滚段信息就越少。

5、对大事务指定回滚段

通过以下语句可以指定事务的回滚段:

SET TRANSACTION USE ROLLBACK SEGMENTrollback_segment;

我觉得标红的都是可以考虑的方案,

我的解决方案包括了

1.while(rs.next()){
     
    }去掉,不在rs循环内执行update,而是把rs数据全部读出,然后在进行逐条update,

2.无非就是标红的那些了

相关推荐