工作原因,對開發服務器的數據庫進行了遷移,實際執行操作之前查了一下遷移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參數,若是第一次搜索資料時就找到了這個參數,或許我也就懶得看其他的內容了,說到底,我只是條懶狗。