oracle資料庫裡存放著各種各樣的資料,其中有一些資料表會隨著時間的推移,越來越大。如交友聊天的日誌、簡訊收發的日誌、生產系統的日誌、動態**發布系統的日誌等等。這樣的資訊又和時間緊密相關,有沒有辦法讓這些日誌錶能按時間自動分割成歷史年月(如log200308,log200309)的表呢? 請看看用儲存過程定期分割表的方法吧。
一、問題的引出
1.初學資料庫時只知道用delete來刪除表裡的資料。但在oracle資料庫裡,大量delete記錄後,並不能釋放表所占用的物理空間,這裡面有乙個高水位的概念,所以我們不能用delete來分割表。
2.用重新命名(rename)表的方法
(1) 先建乙個和原來日誌表(假如是log)資料結構一模一樣的新錶(如log_new),建約束、索引及指定欄位的預設值;
(2) 重新命名表log到log_yyyymm;
要注意的問題是oltp系統可能會因為dml操作阻礙重新命名執行成功,出現ora-00054資源正忙的錯誤提示,需要試多次才能成功。
(3) 重新命名表log_new到log。
這樣應用程式不用修改(受影響的時間僅幾秒鐘),日誌表就被截斷分割了。
上述步驟可以在oracle裡用儲存過程來實現。
二、用儲存過程來分割表
可以看到在重新命名表的方法中,步驟(2)是個關鍵。下面這個rename_table過程會在有鎖阻礙的情況下用遞迴的方式重試100次。
重新命名原始表到目標表的儲存過程rename_table:
create or replace procedure rename_table(source_name in varchar2,
target_name in varchar2,
times in out number)
is
query_str varchar2(4000);
source_name1 varchar2(64);
target_name1 varchar2(64);
cursor c1 is select segment_name from user_segments
where segment_name=upper(source_name);
dummy c1%rowtype;
cursor c2 is select segment_name from user_segments
where segment_name=upper(target_name);
dummy2 c2%rowtype;
begin
source_name1:=source_name;
target_name1:=target_name;
open c1;
fetch c1 into dummy;
-- if c1%found then
-- dbms_output.put_line(source_name1||'exist!');
-- end if;
open c2;
fetch c2 into dummy2;
-- if c2%notfound then
-- dbms_output.put_line(target_name1||'not exist!');
-- end if;
if c2%notfound and c1%found then
query_str :='alter table '||source_name1||' rename to '
||target_name1;
execute immediate query_str;
dbms_output.put_line('rename success!');
end if;
close c1;
close c2;
exception
when others then
times:=times+1;
if times<100 then
-- dbms_output.put_line('times:'||times);
rename_table(source_name1,target_name1,times);
else
dbms_output.put_line(sqlerrm);
dbms_output.put_line('error over 100 times,exit');
end if;
end;
/截斷分割log表的儲存過程log_history:
create or replace procedure log_history
isquery_str varchar2(32767);
year_month varchar2(8);
times number;
begin
select to_char(sysdate-15,'yyyymmdd') into year_month from dual;
times:=0;
query_str :='create table log_new pctfree 10 pctused 80
as select * from log where 1=2';
execute immediate query_str;
query_str :='alter table log_new add constraints log_'
||year_month||'_pk
primary key (id) tablespace indx nologging pctfree 10';
execute immediate query_str;
query_str :='alter table log_his modify logtime default sysdate';
execute immediate query_str;
query_str :='create index log_'||year_month||'_logtime on log(logtime)
tablespace indx nologging pctfree 10';
execute immediate query_str;
rename_table('log','log'||year_month,times);
query_str :='alter table log_new rename to log';
execute immediate query_str;
end;
/
當然您工作環境的日誌表可能和我這個做例子的日誌表結構上有所不同,約束條件、索引和預設值都不盡相同。只要稍加修改就可以了。
三、使用者需要有create any table系統許可權(不是角色裡包含的許可權)
因為在執行儲存過程時,由角色賦予的許可權會失效, 所以執行log_history的使用者一定要有dba單獨賦予的create any table系統許可權。
最後在os裡定時每月一號凌晨0:00分執行log_history,讓儲存過程定期分割表。
如果要分割的日誌表很多,模仿log_history可以寫很多類似的儲存過程來分割不同專案裡的日誌表。然後讓os按月,按周或者不定期的執行這些儲存過程, 管理員只要檢視日誌就可以了。
四、其它注意事項
如果應用程式有bug,可能對在用原始日誌表產生長期不能釋放的鎖,執行log_history重新命名會不成功。
這時dba可以檢視資料字典:
select object_id,session_id,locked_mode from v$locked_object;select t2.username,t2.sid,t2.serial#,t2.logon_time
from v$locked_object t1,v$session t2
where t1.session_id=t2.sid order by t2.logon_time;
如果有長期出現的一模一樣的列(包括登入時間),可能是沒有釋放的鎖。
我們要在執行分割日誌表的儲存過程前,用下面sql語句殺掉長期沒有釋放非正常的鎖:
alter system kill session 'sid,serial#';
五、結束語
用上面介紹的儲存過程定期分割日誌表有很大的靈活性。歷史資料不僅查詢方便,轉移和備份起來也都很容易。unix和windows平台的都可以使用。對伺服器硬碟空間較小的中小型公司意義尤其明顯。
在ORACLE裡用儲存過程定期分割表
oracle資料庫裡存放著各種各樣的資料,其中有一些資料表會隨著時間的推移,越來越大。如交友聊天的日誌 簡訊收發的日誌 生產系統的日誌 動態 發布系統的日誌等等。這樣的資訊又和時間緊密相關,有沒有辦法 讓這些日誌錶能到時間自動分割成歷史年月 如log200308,log200309 的表呢?請看看我...
在ORACLE裡用儲存過程定期分割表
oracle資料庫裡存放著各種各樣的資料,其中有一些資料表會隨著時間的推移,越來越大。如交友聊天的日誌 簡訊收發的日誌 生產系統的日誌 動態 發布系統的日誌等等。這樣的資訊又和時間緊密相關,有沒有辦法 讓這些日誌錶能到時間自動分割成歷史年月 如log200308,log200309 的表呢?請看看我...
在ORACLE裡用儲存過程定期分割表
oracle資料庫裡存放著各種各樣的資料,其中有一些資料表會隨著時間的推移,越來越大。如交友聊天的日誌 簡訊收發的日誌 生產系統的日誌 動態 發布系統的日誌等等。這樣的資訊又和時間緊密相關,有沒有辦法 讓這些日誌錶能到時間自動分割成歷史年月 如log200308,log200309 的表呢?請看看我...