工作原因,對開發伺服器的資料庫進行了遷移,實際執行操作之前查了一下遷移oracle資料庫的可行方案,最後用了 exp/imp 進行匯出匯入(這個比較簡單),以及附帶看了一些表空間相關的知識點(重點喲),下面是一些記錄。
一、exp匯出整個例項資料
exp ****/****@**** full=y compress=n file=G:\Share\compress_N\****.dmp log=G:\Share\compress_N\exp.log
上述命令是我匯出時採用的命令,其中使用者名稱,密碼及例項名應根據需求自己修改。
full 引數代表匯出的是整個例項的資料,若只想匯出特定使用者的相關資料,可通過owner引數指定。
compress引數預設為Y, 這裡並不是壓縮dmp檔案的意思,而是代表對該表空間下的檔案碎片進行整理(資料存放的時候可能會在磁碟產生的碎片檔案),同時很重要的一點是指定為Y時,表的尺寸會定義為該表當前實際佔用的空間尺寸。即:或許你曾經在某個表空間記憶體放了一千萬的資料,使得檔案佔用了10G或者更大的空間。後來因為某些原因刪除了資料,而且也沒有對錶空間進行壓縮整理,此時使用exp匯出並指定compress為y,在使用imp匯入至新的例項後,你會發現,即使表空間內並未儲存任何大資料,但是新生成的表空間檔案仍然佔用了10個G或者更大的空間。
grant引數是匯出授權相關的資訊,預設為Y,此處便省略了,但是這一點還是要知道的。
關於exp的其他引數,可以使用 exp -help 自行查閱,也可閱讀 ColinJames--Oracle資料庫exp和imp方式導資料 進行檢視。
二、imp匯入的相關操作
1、建立一個資料庫例項,根據情況選擇資料庫存放位置,為了方便管理,建議不要使用預設的存放位置。
2、如果匯出時使用了dba許可權的使用者,那麼在匯出時也應該使用具有dba許可權的使用者。所以,根據匯出時的使用者是否為資料庫自帶使用者,是否具有dba許可權判斷是否需要建立使用者並賦予DBA許可權。如果匯出時使用的是資料庫自建使用者,例如system,sys,而且也不想修改資料庫檔案所在的位置,請跳過步驟3、步驟4、步驟5建立表空間和使用者的操作。
3、在新資料庫下建立與原例項相同的表空間。(匯入後補充:根據匯入時的日誌看,直接使用imp應該也可以匯入,但是生成的表空間檔案應該是與原來的存放位置相同,筆者未進行直接匯入的測試,請自行測試。如果不想將表空間檔案與原資料庫的目錄一致,還是需要建立好以後再匯入)
建立時可使用下面的命令在原資料庫下執行,批量生成sql語句。
--推薦使用本條語句
--建立所有已存在的表空間
-- tsds 意指 tablespace definition statement
--v$tablespace是一個內建檢視,可以查詢本例項下的所有表空間,其餘與上方類似。
select 'create tablespace ' || space ||
' Datafile "資料檔案存放路徑' || space ||
'.dbf" size 20M autoextend on next 20M maxsize unlimited extent management local;' as tsds
from (select name as space from v$tablespace where name not in('USERS','SYSTEM','SYSAUX','TEMP'));
--建立所有使用者的預設表空間
--dba_users 記憶體放著使用者及表空間的對應關係,產生的語句會建立所有使用者的預設表空間,資料檔案的初始大小及擴充套件容量可自行修改,另外生成的sql語句內需要自行指定檔案存放位置並將 “ 替換為 ‘ 才可正常執行。(一般文字處理軟體都可以批量替換)
select 'create tablespace ' || space ||
' Datafile "資料檔案存放路徑' || space ||
'.dbf" size 20M autoextend on next 20M maxsize unlimited extent management local;' as tsds
from (select distinct default_tablespace as space from dba_users u where u.default_tablespace not in('USERS','SYSTEM','SYSAUX','TEMP') );
--鑑於有些人建使用者時忘記調整預設表空間,建表時卻指定了某個表空間,所以最好使用第一條語句
4、建立使用者並指定預設表空間。
同樣提供瞭如下生成sql的語句,需要在原資料庫下執行
-- 此處需要自行指定使用者密碼
select 'create user ' || username ||
' identified by **** account unlock default tablespace ' ||
spacename || ' ;' as userds
from (select username as username, default_tablespace as spacename
from dba_users
where username not in ('ANONYMOUS',
'CTXSYS',
'DBSNMP',
'DIP',
'DMSYS',
'EXFSYS',
'MDDATA',
'MDSYS',
'MGMT_VIEW',
'OLAPSYS',
'ORDPLUGINS',
'ORDSYS',
'OUTLN',
'SCOTT',
'SI_INFORMTN_SCHEMA',
'SYS',
'SYSMAN',
'SYSTEM',
'TSMSYS',
'WMSYS',
'XDB')
order by username );
5、登入新的資料庫,執行生成的 tsds 和 user_ds 語句,注意執行順序,先建立表空間,在建立使用者。
6、使用imp執行匯入。
imp ****/****@**** full=y file=G:\Share\compress_N\****.dmp log=G:\Share\compress_N\imp.log
如果沒有提前建立使用者,則使用者密碼與原庫相同。
imp有一個ignore的引數,代表忽略建立錯誤,預設為N,此處並未開啟。在匯入過程中會出現諸多表空間及使用者的建立錯誤,此類錯誤可忽略。
其餘引數及引數含義可在命令列使用 imp -help自行查閱。
至此,imp匯入就算結束了。
三、資料庫表空間物理檔案縮小
會有這個小節是因為第一次匯出的時候不知道加入 compress = n 的引數,導致只有很少資料量的一個庫佔用了30多G的磁碟空間,為了減少佔用,看了一些跟壓縮表空間相關的內容,在此做些記錄。
*1. 總結提前宣告
- 有一些概念還沒說到,如果你不懂什麼意思,可以先了解一下,看完其他的內容以後再回來看第二遍。
- 並不是物理檔案過大就需要將其縮小,我這邊進行處理是因為這只是個開發庫備份,另外我對這個庫也足夠清楚,知道這個表空間檔案不正常,而且也不會再向這個表空間內寫入資料。
- 在使用
EXP
匯出時指定 compress引數,可以有效的減小物理檔案的大小。採用預設值匯出匯入後物理檔案是30多G,設定引數後是15G多一些,減少了一半。至於為什麼還有這麼多,在我檢視段資訊後發現了幾個異常的表和索引,這幾個異常物件初始時分配了最高4G的空間,是主要禍首。以我自己的操作過程及現在的理解來看,如果沒有那幾個初始值異常大的表定義和索引定義,匯出的結果是符合我的預期的。 - 以縮小表空間為關鍵字搜尋,很容易就會找到一些文章告訴你要先使用
shrink space
壓縮段,然後通過resize
命令縮小表空間的物理檔案,不過在我實測後發現,這種方法的適用範圍很苛刻,它要求你所操作的段資料剛好位於表空間的末尾,即你所操作的資料段剛好佔據著已使用的最大塊,此時對段進行壓縮操作,然後resize,表空間檔案才可以縮小。 - 舉例來說,假設一個表空間內有AB兩張表,每十萬資料佔用10M磁碟空間,我們分兩種情況來看。第一種情況,先向A表內寫入100W資料,然後刪除A表內的40W資料,佔用空間少了40M,這時執行
shrink space
,resize 60M
,物理檔案確實會按預期縮小。第二種情況,先向A表寫入了100W資料,又向B表寫入了10W資料,最後將A表內的資料刪除了90W,此時對A段Shrink,對錶空間Resize,然後就會觸發 ORA-03297:檔案包含在請求的RESIZE值以外使用的資料,這裡面會涉及到資料塊的概念,因為B表內寫入的資料佔用了表空間內更靠前的資料塊,沒辦法調整物理檔案大小,而且實際情況下,資料的寫入是無法預期的,所以此方法無用。 Shrink Space
做了什麼?需要說明一下,這是在Oracle 10g中新增的功能,用來優化資料段的高水位(HWM)問題,高水位會導致查詢時掃描的資料塊過多,影響查詢時的速度,所以需要優化。關於高水位的內容,可檢視 arctic_fox的文章 - oracle 高水位線詳解,另外需要注意的是shrink segment
的操作會改變資料的rowid,也就是改變了資料的物理位置,該命令會自動重建索引,但是會導致已開啟的遊標失效,如果要在生產環境使用,必須要慎重。- EXP/IMP,這應該是最簡單的方法了,也是我採用的方法。在對錶空間內需要整理的段進行收縮整理後,查詢
dba_free_space
可以看到處於未使用狀態的區間編號,如果這個表空間不會再次插入新資料,可以指定當前使用者重新匯出,再次匯入後,可以發現物理檔案已經縮小到了自己可接受的程度。 move tablesapce
的方法只是看了看,未進行實操驗證,如果您無法或不方便執行資料的匯出匯入。建議點選一澤漣漪 - Oracle收縮表空間檢視原文了解相關內容,還有這篇菜鳥程式設計師 - ORACLE修改表空間方法
2、一些預備知識
-
oracle內建一些表和檢視,以user_開頭的可以查詢當前使用者擁有的所有物件,以all_開頭的可以訪問當前使用者擁有訪問許可權的物件(可以是其他使用者的物件),以dba_開頭的需要dba許可權,可以訪問資料庫內的所有物件。
-
表空間的儲存結構在邏輯上的資料結構如下:
tablespace(表空間) - segment(段) - extent(區) - block(塊)
資料儲存在Block資料塊中,資料塊對應在物理磁碟上;一個或多個連續的資料塊組成區,區不能跨段,一個區只屬於一個段;所以,區也只是一個邏輯上的概念,區與實際儲存資料的塊關聯,段資訊的彙總展示會更簡單明瞭。
段是由區組成,段中會存在一個初始區,用於存放資料,空間不夠時會自動分配新的區,實際上就是分配了新的資料塊存放資料,區號是按順序排列的,塊可以優先使用當前未分配的空間(可以在dba_free_space中檢視)。
表空間則是段的容器,一般oracle會為表或索引建立一個段,用於存放表或索引的資料,稱為表段或索引段,每個分割槽表也是一個獨立的段,關於段的具體型別,可以在dba_segments中檢視 segment_type欄位的標註。
另外,在資料庫安裝時會有一個介面顯示系統的塊大小,預設為8K,也可以通過
select value from v$parameter where name='db_block_size'
自行查詢資料塊的預設值。關於表空間結構更詳細的描述,請自行檢視 oracle 物理結構(表空間,段區塊)_tyhawk的部落格-CSDN部落格
-
幾個內建物件
dba_data_files
可以在這裡檢視錶空間的一些資料,比如物理檔案位置,表空間大小,是否可用,是否自動擴充套件,擴充套件大小等資訊,file_id可以在這裡取(file_id)dba_segments
可以檢視段的資訊,比如段的所有者,段的型別,段的名字等dba_extents
和上面類似,可以檢視所屬段的資訊,分割槽ID(extents_id)以及塊ID(block_id,區的起始塊),另外查詢的時候最好指定file_id,不然會很慢。
v$datafile
可以檢視偏向物理檔案的一些資訊,file_id可以在這裡取(file#)
dba_free_space
可以檢視當前表空間檔案的未使用區間,如果查詢到了過多的結果,其實也是當前表空間內碎片過多
- 幾個查詢語句
--查詢表空間及其物理檔案位置
select t1.name,t2.name
from v$tablespace t1,v$datafile t2
where t1.ts# = t2.ts#;
--查詢資料庫的 block_size
select value from v$parameter where name='db_block_size';
--檢視指定表空間內指定段的分割槽資訊
select * from dba_extents t where t.FILE_ID = 00 and t.segment_name = 'XXX';
- 整理段的語句
--需要先開啟行移動,否則有 ORA-10636 ROW MoVEMENT is not enabled的報錯
alter table history.TB_FT_BALANCE enable row movement;
alter table history.TB_FT_BALANCE shrink space;
--shrink segment的操作會改變資料的rowid
--另外看到有人說執行shrink space時之前的遊標會失效,生產上還是要慎重一點
alter table history.TB_FT_BALANCE deallocate unused;
alter table history.TB_FT_BALANCE disable row movement;
關於 Row Movement,可以看這裡:Enmotech - 深入解析 Row Movement 的原理和效能影響與關聯
上面說了,oracle一般會為表分配一個段,所以可以只操作曾經有過大量資料後來又被刪除的表,壓縮表段其實也就是整理表碎片
- 改變表空間物理檔案大小的語句如下
alter database datafile '/u01/test01/t11.dbf' resize 5m;
--引數自行修改,另外如果指定的空間大小無法存放已有的資料,此處會報錯
--至於具體應該指定的數值可使用如下語句獲取
--獲取表空間檔案編號
select file#,name from v$datafile; --此處取出表空間檔案的file#編號
--通過最大塊確定指定檔案佔用的空間
select (max(block_id) + blocks)*8/1024 from dba_extents where file_id={$file#}; --單位為 M,塊大小採用預設值 8K。
-- alter時resize的引數必須要大過查詢結果
如果表空間已經被佔用過,即使將表資料刪除,上面的語句查詢出來的結果也並不會差太多,因為被佔用的空間並沒有被釋放。
3、個人操作
需要宣告的是在進行此處的操作時,我並不知道加入 compress 引數的做法,此時的物理檔案也有30多G。
先使用下方語句檢視了一下佔用空間較大的segment
--指定file編號,獲取當前檔案內的段資訊
select t.owner,
t.segment_name,
t.partition_name, --分割槽名,一個段可能有多個分割槽
t.segment_type, --段型別
t.tablespace_name,
t.BYTES/(1024*1024) as MB, --段大小,單位是MB
t.BYTES, --段的大小,單位是 byte
t.initial_extent --初始化時分配的大小
from dba_segments t
where t.relative_fno = {$file#}
order by t.BYTES desc;
找到了一個佔用達1G,但實際沒有資料的的表段,使用如下語句進行表段的整理:
alter table history.XXXX enable row movement;
alter table history.XXXX shrink space; --shrink segment的操作會改變資料的rowid,使已開啟的遊標失效
alter table history.XXXX deallocate unused;
alter table history.XXXX disable row movement;
操作後可再次執行段資訊查詢語句,會發現該段佔用空間明顯縮小。
發現上述操作確實可以使得表段佔用空間減少後,我並沒有去懷疑網上直接resize檔案的做法是否可行,反而是在檢視了
dba_segements
的查詢結果後,發現需要執行該套操作的表段實在有點多,出於偷懶的想法,才重新進行百度,並找到了加入 compress 引數的建議。在實操驗證後,發現物理檔案縮小到了15G,只是這個大小仍然遠遠超過我的承受範圍,所以我還是準備採用上面說到的方法進行操作。
在一張幾十萬資料的測試表內刪除了資料後,對該表段執行了壓縮操作,此時,有些文章就會告訴你可以進行alter database datafile 'xxx' resize 0m
的操作,利用sql查詢到當前實際佔用的空間後,實操進行驗證,過稱中觸發了ORA-03297的錯誤,當時猜測是因為釋放出來的空間仍以碎片形式存在,資料庫系統並不會將資料依次前移去填充空白碎片區間,於是自己分兩種情況進行了驗證,得出了這種方法並不通用的結論,驗證時區分的兩種情況以及對 shrink space
的說明請檢視本節開篇的總結了解。
在明確知道壓縮段無法滿足我的需求後,整理得到的碎片空間會如何利用又引起了我的興趣,於是在另外一個表空間內我對兩張表分別寫入了幾百萬資料,然後使用delete刪除了一部分資料並整理得到了一部分未使用的碎片空間,查詢
dba_free_space
後,得到未使用的block_id範圍。在之前的兩張表AB以及一個未整理過的C表內插入資料,發現碎片的block會按照使用者的順序進行分配,也就代表在A表內整理得到的空間並不侷限於只能A使用,其他的 B,C表也可以分配空白區域的block,其實到這裡,我才明白了Extent存在的意義,段所代表的上層無需知道實際儲存資料的地址,只需要知道區號即可,根據extent_id再去查詢實際使用的block.
到這之後,無奈又得重新找方法,然後找到了 一澤漣漪 - Oracle收縮表空間這篇文章,博主的方法其實是將資料移到了新的表空間,然後將其指定為原使用者的預設表空間。看過之後,因為擔心 lob 欄位的資料會不會丟失,也不知道當前表空間下的SP,Function會怎麼樣,所以我並沒有按照這種方法操作。如果想使用move的方法,建議同時閱讀一下這篇文章菜鳥程式設計師 - ORACLE修改表空間方法,這裡面有對 lob的一些說明。
進展到這其實陷入了一種僵局,直到我無意中看到dba_segments
的 initial_extent欄位,發現幾個物件的初始值過大。於是使用工具 rebuild 了這幾張表,重建之後查詢段資訊發現多了幾條段名稱亂碼的資料,該工具重建時沒有釋放表佔用的空間,如果讀者有重建表的需求,建議先保留表相關的定義及表資料,然後刪除重建。
delete、drop、truncate的區別可檢視weixin_33871366 - oracle中delete drop truncate的用法和區別。
對於未被釋放的段,查詢無果後,決定重新對該使用者執行 exp/imp 的操作,操作後驚喜的發現,新的表空間內已經不存在那幾個亂碼的段資訊,而且物理檔案也縮小到了我可以接受的程度,至此,縮小表空間的物理檔案終於是找到了一個合適的方法。在此之後也想到了這和move tablespace其實本質上是一樣的。
4、Move Tablespace
本節是對上面提到的兩篇文章的一些記錄,move tablespace的方法我並沒有測試,另外我感覺move更有用的點應該是可以移動表到新的表空間(建表時搞錯表空間的經歷,不會只有我有吧 >_<)
-
移動資料段至新的表空間時,對錶和索引的是一種處理方式,對lob欄位是另一種處理方式
-
系統會為 lob 欄位分配一個segment 用於存放資料,關於兩種處理方式可以看獲取更多的說明。
上方兩條說明是為了下面的操作步驟做準備,下面開始使用move。
-
新建一個表空間
create tablespace TS_New Datafile 'E:\TS_New.dbf' size 200M autoextend on next 100M maxsize unlimited extent management local;
-
生成 table 的move語句
select 'alter table ' || owner || '.' || segment_name || ' move tablespace TS_New;' sqltext from dba_segments where tablespace_name = 'TS_HISTORY' and segment_type='TABLE';
-
生成 index 的move語句
select 'alter index ' || owner || '.' || segment_name || ' rebuild tablespace TS_New;' sqltext from dba_segments where tablespace_name = 'TS_HISTORY' and segment_type='INDEX';
-
生成 lob段的 move語句
select 'alter table ' || owner || '.' || table_name || ' move lob(' || column_name || ') store as(tablespace TS_New);' sqltext from dba_lobs where tablespace_name = 'TS_HISTORY';
lob段資料的遷移建議檢視菜鳥程式設計師 - ORACLE修改表空間方法,裡面對語句及引數介紹的比較明白,而且還考慮了表,分割槽表的情況,這裡我就不搬過來了。
5、回顧一下
耗費了N多N多的時間,我得到了什麼呢?最重要的其實倒不是縮小物理檔案的方法,而是對錶有了更深的理解。以前是在資料存放容器的層次看待資料庫中的表,我只知道資料放進去了,用的時候來拿就好。現在倒是瞭解了資料表的一些更細節的知識點。Segment,Extent,Block這三個概念以及相關的幾個系統檢視,這就是收穫。
與此相關的,也意外的瞭解了高水位,段整理,轉移表空間的一些知識。
最後當然是本次問題的解決者,exp命令中的compress引數,若是第一次搜尋資料時就找到了這個引數,或許我也就懶得看其他的內容了,說到底,我只是條懶狗。