我遇到了一个需要解决的问题,其中Master Db中有一个名为Scenarios的表,其中包含我必须找到大小的所有Tablespace的详细信息。 O / P应包含表大小(实际消耗)和索引大小以及行数。
因此,我编写了一个大小调整脚本(PL / SQL)来查找该特定数据库服务器上所有表空间的大小。
但是我跑了好几天之后才得到这个特殊的例外。
ORA-01555:快照太旧:回滚段号9,名称“_SYSSMU9 $”太小
我不确定是什么原因造成的,因为数据量不是很大。
我正在附上剧本
SET SERVEROUTPUT ON size '10000000'
declare
TYPE cur_typ IS REF CURSOR;
a_Temp number := 0;
x_Total number := 0;
i number := 0;
c_cursor cur_typ;
query_str varchar2(500);
num_long Long;
currentScenarioDB nvarchar2(255);
tableExists number := 0;
scenarioId varchar2(50);
scenarioName varchar2(100);
dbIdentifier nvarchar2(50);
queryToFindScenarioNameAndId varchar2(400) := 'select scenarioId,name from scenarios where dbidentifier = ';
selectQuery varchar2(400) := 'select scenarioId,name from scenarios where dbidentifier = ';
insertStatement varchar2(2000) := 'Insert Into ScenarioTableAndIndexSize values (:1,:2,:3,:4,:5,:6,:7) ';
-- scenarioId,scenarioname,,dbIdentifier,tablename,dataSize,IndexSize,rowNumber
tableIndexSize number := 0;
numOfRows number := 0;
rowNum number := 0;
tableDataSize number := 0;
Cursor getScenarioDb is select dbidentifier from scenarios where dbidentifier IN (select Distinct(TABLESPACE_NAME) from dba_tables);
begin
DBMS_OUTPUT.ENABLE(10000000);
execute immediate 'truncate table ScenarioTableAndIndexSize';
open getScenarioDb;
fetch getScenarioDb into currentScenarioDB;
while getScenarioDb%found
loop
queryToFindScenarioNameAndId := selectQuery || '''' || currentScenarioDB || '''';
execute immediate queryToFindScenarioNameAndId into scenarioId,scenarioName;
declare
queryToFindNoofRows varchar2(1000);
queryConstruct varchar2(32767) := '';
outputTableInScenarioDb nvarchar2(256);
Cursor getTablesInScenario is select DISTINCT TABLE_NAME from dba_tables where owner = currentScenarioDB and TABLE_NAME not like 'BIN%' and table_name != 'SCENARIOTABLEANDINDEXSIZE' order by table_name;
begin
tableExists := 0;
open getTablesInScenario;
fetch getTablesInScenario into outputTableInScenarioDb;
while getTablesInScenario%found
loop
queryConstruct := 'select nvl( sum (';
tableIndexSize := 0;
tableDataSize := 0;
numOfRows := 0;
queryToFindNoofRows := 'select count(*) from '|| currentScenarioDB || '.' ||outputTableInScenarioDb;
execute immediate queryToFindNoofRows into numOfRows;
if numOfRows > 0 then
---------------------------Beginning Of Section to find Table data Size------------------------------------------------------------------------------------------------
declare
Cursor getColumnsInTables is select * from dba_tab_columns where Table_Name = outputTableInScenarioDb and owner = currentScenarioDB;
dbaTabColumnRow dba_tab_columns%rowtype;
dataType varchar2(40);
fields varchar2(1000);
begin
open getColumnsInTables;
fetch getColumnsInTables Into dbaTabColumnRow;
while getColumnsInTables%found
loop
dataType := dbaTabColumnRow.DATA_TYPE;
if dataType = 'CLOB' then
fields := 'nvl(DBMS_LOB.GETLENGTH(' || dbaTabColumnRow.COLUMN_NAME ||'),0)';
elsif dataType = 'BLOB' then
fields := 'nvl(DBMS_LOB.GETLENGTH('|| dbaTabColumnRow.COLUMN_NAME ||'),0)';
elsif dataType = 'LONG' then
fields := 'nvl(VSIZE(''''),0)';
x_Total := 0;
query_str := 'SELECT ' || dbaTabColumnRow.COLUMN_NAME || ' FROM ' || currentScenarioDB || '.' ||outputTableInScenarioDb;
OPEN c_cursor FOR query_str;
LOOP
FETCH c_cursor INTO num_long;
EXIT WHEN c_cursor%NOTFOUND;
a_Temp:=length(num_long);
x_Total:= x_Total + a_Temp;
END LOOP;
CLOSE c_cursor;
else
fields := 'nvl(vsize(' || dbaTabColumnRow.COLUMN_NAME || '),0)';
end if;
fetch getColumnsInTables Into dbaTabColumnRow;
if getColumnsInTables%found then
queryConstruct := queryConstruct || fields||'+';
else
queryConstruct := queryConstruct || fields;
end if;
end loop;
end;
queryConstruct := queryConstruct || '),0) as sizeOfTable from ' || currentScenarioDB || '.' ||outputTableInScenarioDb;
--dbms_output.put_line(queryConstruct);
execute immediate queryConstruct into tableDataSize;
---------------------------End Of Section to find Table data Size-------------------------------------------------------------------------------------------------------------
---------------Section To find index size
declare
Index_Name nvarchar2(4000);
sql_statement varchar2(1000) := 'select nvl(USED_SPACE,0) from index_stats';
stat1 varchar2(1000) := 'analyze index ';
stat2 varchar2(1000) := ' validate structure';
stat3 varchar2(2000) := '';
size1 number := 0;
cursor indexOnTable is select INDEX_NAME from dba_indexes where tablespace_name = currentScenarioDB and table_name = outputTableInScenarioDb and index_type = 'NORMAL';
begin
open indexOnTable;
fetch indexOnTable into Index_Name;
while indexOnTable%found
loop
stat3 := stat1 || currentScenarioDB ||'.' ||Index_Name || stat2;
execute immediate stat3;
execute immediate sql_statement into size1;
tableIndexSize := tableIndexSize + size1;
fetch indexOnTable into Index_Name;
end loop;
close indexOnTable;
end;
-----end of section to find index size
else
rowNum := rowNum + 1;
end if;
tableDataSize := x_Total + tableDataSize;
execute immediate insertStatement using scenarioId,scenarioName,currentScenarioDB,outputTableInScenarioDb,tableDataSize,tableIndexSize,numOfRows;
x_Total := 0;
fetch getTablesInScenario into outputTableInScenarioDb;
end loop;
end;
fetch getScenarioDb into currentScenarioDB;
end loop;
close getScenarioDb;
end;
表的大小是这样找到的:
仅指定用户对所有数据库具有权限
然后我将所有这些加起来以找到表中的总数据大小。
有人可以告诉我我做错了什么或我该怎么做才能解决错误?
感谢。
答案 0 :(得分:8)
“有人可以告诉我我在做什么 错“
从哪里开始?
<强> ORA-01555 强>
这在长时间运行的查询中会发生。 Oracle的读取一致性策略保证结果集中的最后一条记录与结果集的第一条记录保持一致。换句话说,我们的查询不会返回在我们发出查询后提交的其他会话中所做的更改。 Oracle通过将UNDO表空间中的记录替换为任何已更改的记录来完成此操作。当它无法做到这一点时,它会抛出SNAPSHOT TOO OLD异常。这意味着Oracle不再具有提供读一致视图所需的旧版本记录。
Oracle不再拥有数据的一个常见原因是因为我们的长时间运行查询是PL / SQL游标循环,它发出COMMIT语句。正如您所知,COMMIT表示事务的结束,并释放Oracle为我们的会话保留的任何锁定。这显然包括我们会议对UNDO表空间的兴趣;然后,Oracle可以自由地覆盖包含读取一致性所需数据的UNDO块。
在您的情况下,COMMIT语句是包含任何DDL语句的隐式语句 - 包括ANALYZE。这可能无关紧要,但似乎有人在您的程序运行时更新SCENARIOS表,这可能需要几天的时间来保证。
使用ANALYZE
这有几个原因很糟糕。首先是它已经被弃用了很长一段时间:你是10g,你应该使用DBMS_STATS to gather statistics。但是等等,还有更多。
收集统计数据并不是应该经常进行的事情。在大多数系统中,统计数据实现了稳定的平台,即使在几个月之后它们也足够准确。因此,频繁收集统计数据充其量只是浪费周期。情况可能更糟:新的统计数据产生的效率低于现有计划的风险。因此,实际上统计收集应该以受控方式进行。 DBMS_STATS的一个优点是我们可以将其配置为监视应用于表的更改率,并仅在达到某种过期时重新收集统计信息。 Find out more.
当然你只是使用ANALYZE来获取索引的最新空间使用量,这让我想到了第三点:
<强>精神错乱强>
您选择了您感兴趣的所有表格的每一行,并总计所有列的实际大小,如果我的方法正确,则为每列提供单独的查询。这太疯狂了。
Oracle提供的视图显示给定表使用的空间量。尽管USER_EXTENTS也可用,但USER_SEGMENTS应该足够了。 SEGMENT_NAME是索引或表名。汇总BYTES列将为您提供每个表的足迹的确切大小。
当然,其中一些已分配的范围将是空的,因此您可能会认为这些数字会略微过高估计。但是:
“但背后的整个动机 写这整个PL / SQL脚本是 获得ACTUAL不分配空间“
好的,让我们解决这个问题。你的脚本的主要问题是它解决了RBAR的问题;实际上比RBARBAC更糟糕。因此,您发出一个查询矩阵,每个表对应一行的每一列。 SQL是一种基于集合的语言,如果我们这样对待它会更好。
此过程汇编一个动态查询,该查询汇编单个SELECT以获取给定表的总大小和记录数。
create or replace procedure get_table_size
( p_tabn in user_tables.table_name%type
, p_num_rows out pls_integer
, p_tot_size out pls_integer )
is
stmt varchar2(32767) := 'select count(*), sum(';
n_rows pls_integer;
n_size pls_integer;
begin
for r in ( select column_name, data_type, column_id
from user_tab_columns
where table_name = p_tabn
order by column_id)
loop
if r.column_id != 1
then
stmt := stmt ||'+';
end if;
stmt := stmt || 'nvl(';
if r.data_type in ('CLOB', 'BLOB', 'BFILE')
then
stmt := stmt || ' dbms_lob.getlength('||r.column_name||')';
else
stmt := stmt || ' vsize('||r.column_name||')';
end if;
stmt := stmt || 'nvl)';
end loop;
stmt := stmt || ') from '||p_tabn;
execute immediate stmt into n_rows, n_size;
p_num_rows := n_rows;
p_tot_size := n_size;
end;
/
它不包括块头开销(每行3个字节),但这只是一个简单的算术问题。
这是在行动:
SQL> desc t34
Name Null? Type
----------------------------------------- -------- ----------------------------
SEQ_NUM NUMBER
UNIQUE_ID NUMBER
NAME VARCHAR2(20 CHAR)
LONG_COL CLOB
SQL>
SQL> set timing on
SQL> var n1 number
SQL> var n2 number
SQL> exec get_table_size('T34', p_num_rows=>:n1, p_tot_size=>:n2)
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.89
SQL> print n1
N1
----------
11
SQL> print n2
N2
----------
135416
SQL>
小桌子,也许是不切实际的快速。这是一个更大的,没有clobs。
SQL> exec get_table_size('BIG_TABLE', p_num_rows=>:n1, p_tot_size=>:n2)
PL/SQL procedure successfully completed.
Elapsed: 00:00:10.65
SQL> print n1
N1
----------
4680640
SQL> print n2
N2
----------
189919606
SQL>
经过的时间仍然很好,嗯?
关于索引的空间,类似的查询将起作用,仅从USER_IND_COLUMNS驱动以获取适当的列名。我认为重新分析索引比较好。它不适用于调整您在CLOB或BLOB列上可能具有的任何TEXT索引的大小。对于那些需要使用CTX_REPORT.INDEX_SIZE()的人,尽管这会生成一个报告,您需要在oder中解析以获取有用的数字(XML格式在这方面可能会有所帮助)。
答案 1 :(得分:1)
您的程序不是最佳的。你在循环中声明了很多游标。每次执行循环时都会进行解析。更聪明的是在顶层定义光标。通过使用批量集合和批量插入,您还可以获得很多性能。如果您可以将代码从PL / sql升级到sql,那么您将获得最佳性能。 这可能并不总是可行,但尝试使用最少的过程代码。
您可以尝试提高数据库的撤消保留率,但考虑到9天后您的快照太旧而突然出现这种情况,它已经非常高了。
我们在谈论什么版本的数据库?
答案 2 :(得分:1)
您收到ORA-01555错误是因为您提取提交。打开游标,在从光标读取时,提交事务。
当您打开游标时,Oracle会在此时保证您将获得哪些数据。如果您或其他用户随后更改了此游标稍后将迭代的数据,Oracle将返回撤消以获取数据未更改时您将看到的原始数据。最终,ORA-01555错误表明Oracle用尽了撤销,无法再回头了。在这种情况下,Oracle会抛出此错误,因为它必须返回太多已提交的事务。
您的代码在任何地方都没有显式COMMIT
,但似乎ANALYZE
语句与所有Oracle DDL一样,在执行之前和之后执行隐式提交。 (顶部的TRUNCATE
语句也会在之前和之后执行隐式提交,但这不是问题,因为您只调用一次。)
我会做的是:
ANALYZE INDEX index_name VALIDATE STRUCTURE
。ANALYZE INDEX ...
语句的情况下运行其余代码,因为其他一切只是查询数据。答案 3 :(得分:0)
Oracle使用多版本并发控制,即它保留旧版本的记录,以便在长时间运行的查询中保持一致的结果,并且只要未提交新版本。
如果长时间运行的查询需要旧版本,但同时丢弃了此版本,则会出现“快照太旧”错误。
避免此错误的最佳方法是使查询更快。另一个是增加UNDO日志的大小。
但是由于你的程序显然已经运行了几天,你真的需要让程序更快,也就是说更快。