Oracle資料庫搬家牽扯出的一些知識點記錄
阿新 • • 發佈:2021-03-09
工作原因,對開發伺服器的資料庫進行了遷移,實際執行操作之前查了一下遷移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方式導資料](https://blog.csdn.net/fghsfeyhdf/article/details/79807690) 進行檢視。
### 二、imp匯入的相關操作
1、建立一個數據庫例項,根據情況選擇資料庫存放位置,為了方便管理,建議不要使用預設的存放位置。
2、如果匯出時使用了dba許可權的使用者,那麼在匯出時也應該使用具有dba許可權的使用者。所以,根據匯出時的使用者是否為資料庫自帶使用者,是否具有dba許可權判斷是否需要建立使用者並賦予DBA許可權。如果匯出時使用的是資料庫自建使用者,例如system,sys,而且也不想修改資料庫檔案所在的位置,請跳過步驟3、步驟4、步驟5建立表空間和使用者的操作。
3、在新資料庫下建立與原例項相同的表空間。(匯入後補充:根據匯入時的日誌看,直接使用imp應該也可以匯入,但是生成的表空間檔案應該是與原來的存放位置相同,筆者未進行直接匯入的測試,請自行測試。如果不想將表空間檔案與原資料庫的目錄一致,還是需要建立好以後再匯入)
建立時可使用下面的命令在==**原資料**==庫下執行,批量生成sql語句。
```` 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的語句,需要在==**原資料庫下**==執行
````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 高水位線詳解](https://www.cnblogs.com/shoshana-kong/p/9209345.html),另外需要注意的是````shrink segment````的操作會改變資料的rowid,也就是改變了資料的物理位置,該命令會自動重建索引,但是會導致已開啟的遊標失效,如果要在生產環境使用,必須要慎重。
* **EXP/IMP**,這應該是最簡單的方法了,也是我採用的方法。在對錶空間內需要整理的段進行收縮整理後,查詢````dba_free_space````可以看到處於未使用狀態的區間編號,如果這個表空間不會再次插入新資料,可以指定當前使用者重新匯出,再次匯入後,可以發現物理檔案已經縮小到了自己可接受的程度。
* ````move tablesapce```` 的方法只是看了看,未進行實操驗證,如果您無法或不方便執行資料的匯出匯入。建議點選[一澤漣漪 - Oracle收縮表空間](https://www.cnblogs.com/ilifeilong/p/7538397.html)檢視原文了解相關內容,還有這篇[菜鳥程式設計師 - ORACLE修改表空間方法](https://www.cnblogs.com/guohu/p/4500638.html)
#### 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部落格](https://blog.csdn.net/tyhawk/article/details/89519928)
* 幾個內建物件
````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```` 可以檢視當前表空間檔案的未使用區間,如果查詢到了過多的結果,其實也是當前表空間內碎片過多
* 幾個查詢語句
````sql
--查詢表空間及其物理檔案位置
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';
````
* 整理段的語句
````sql
--需要先開啟行移動,否則有 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 的原理和效能影響與關聯](https://blog.csdn.net/enmotech/article/details/79294945)
>
> 上面說了,oracle一般會為表分配一個段,所以可以只操作曾經有過大量資料後來又被刪除的表,壓縮表段其實也就是整理表碎片
* 改變表空間物理檔案大小的語句如下
````sql
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
````sql
--指定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,但實際沒有資料的的表段,使用如下語句進行表段的整理:
````sql
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收縮表空間](https://www.cnblogs.com/ilifeilong/p/7538397.html)這篇文章,博主的方法其實是將資料移到了新的表空間,然後將其指定為原使用者的預設表空間。看過之後,因為擔心 lob 欄位的資料會不會丟失,也不知道當前表空間下的SP,Function會怎麼樣,所以我並沒有按照這種方法操作。如果想使用**move**的方法,建議同時閱讀一下這篇文章[菜鳥程式設計師 - ORACLE修改表空間方法](https://www.cnblogs.com/guohu/p/4500638.html),這裡面有對 lob的一些說明。
進展到這其實陷入了一種僵局,直到我無意中看到````dba_segments````的 **initial_extent**欄位,發現幾個物件的初始值過大。於是使用工具 **rebuild** 了這幾張表,重建之後查詢段資訊發現多了幾條段名稱亂碼的資料,該工具重建時沒有釋放表佔用的空間,如果讀者有重建表的需求,建議先保留表相關的定義及表資料,然後刪除重建。
> delete、drop、truncate的區別可檢視[weixin_33871366 - oracle中delete drop truncate的用法和區別](https://blog.csdn.net/weixin_33871366/article/details/85027916?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-9.baidujs&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-9.baidujs)。
對於未被釋放的段,查詢無果後,決定重新對該使用者執行 exp/imp 的操作,操作後驚喜的發現,新的表空間內已經不存在那幾個亂碼的段資訊,而且物理檔案也縮小到了我可以接受的程度,至此,縮小表空間的物理檔案終於是找到了一個合適的方法。在此之後也想到了這和move tablespace其實本質上是一樣的。
#### 4、Move Tablespace
本節是對上面提到的兩篇文章的一些記錄,move tablespace的方法我並沒有測試,另外我感覺move更有用的點應該是可以移動表到新的表空間(建表時搞錯表空間的經歷,不會只有我有吧 >_<)
* 移動資料段至新的表空間時,對錶和索引的是一種處理方式,對**lob**欄位是另一種處理方式
* 系統會為 lob 欄位分配一個segment 用於存放資料,關於兩種處理方式可以看獲取更多的說明。
上方兩條說明是為了下面的操作步驟做準備,下面開始使用move。
* 新建一個表空間
````sql
create tablespace TS_New Datafile 'E:\TS_New.dbf'
size 200M autoextend on next 100M maxsize unlimited extent management local;
````
* 生成 table 的move語句
````sql
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語句
````sql
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語句
````sql
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修改表空間方法](https://www.cnblogs.com/guohu/p/4500638.html),裡面對語句及引數介紹的比較明白,而且還考慮了表,分割槽表的情況,這裡我就不搬過來了。
#### 5、回顧一下
耗費了N多N多的時間,我得到了什麼呢?最重要的其實倒不是縮小物理檔案的方法,而是對錶有了更深的理解。以前是在資料存放容器的層次看待資料庫中的表,我只知道資料放進去了,用的時候來拿就好。現在倒是瞭解了資料表的一些更細節的知識點。Segment,Extent,Block這三個概念以及相關的幾個系統檢視,這就是收穫。
與此相關的,也意外的瞭解了高水位,段整理,轉移表空間的一些知識。
最後當然是本次問題的解決者,exp命令中的compress引數,若是第一次搜尋資料時就找到了這個引數,或許我也就懶得看其他的內容了,說到底,我只是條