《Kingbase護城河》——資料庫儲存空間全景探測與精細化瘦身實戰

「我的磁碟怎麼又滿了?」——資料庫儲存空間全景探測與精細化瘦身實戰

在這裡插入圖片描述

前面幾篇我們聊了跨平台聯調網路的事,也像探長一樣,從好幾千個併發裡面,把搞卡系統的殭屍程序還有行鎖衝突給找出來了。那麼,平時搞資料庫維運的時候,比系統變慢更讓人頭痛的生產級災難,其實就是這四個英文單詞:No space left on device(磁碟空間不足)

磁碟使用率跑到 100% 會怎樣呢?資料庫核心就沒法往磁碟寫交易日誌(WAL)了。這是一個大問題。整個實例馬上就只讀了,有時候甚至直接就核心級當機(Crash)了。那麼業務那邊想寫資料的話,肯定就全癱了。

很多開發同學收到磁碟告警,第一反應就是去資料庫裡敲一條 DELETE。想刪個幾千萬條半年前的日誌資料來救個急。但是詭異的事情就來了:SQL 提示成功了呀,資料也沒了,去伺服器一看,磁碟空間竟然連 1MB 都沒放出來!

那這到底是為什麼呢?

搞資料庫的人,其實不能只看著表面頭痛醫頭。今天我們就跳出單一的 SQL 視角,搞一個從「OS 物理層 -> DB 邏輯層 -> MVCC 核心層 -> 企業架構層」的排查框架出來。也就是說,我們一步步來,怎麼找準空間黑洞,搞明白「刪了資料卻不釋放空間」的底層原因。接著的話,再通過企業層級裡的架構調優,把這個磁碟空間的問題給徹底解決掉。

@[toc]


第一階段:宏觀俯瞰 —— 跨越 OS 與 DB 的雙重視角稽核

收到磁碟告警的時候,其實千萬別急著登入資料庫去亂查。正確的排障標準作業程序(SOP),通常來說是要先看作業系統(OS)的物理層。接著再到資料庫(DB)的邏輯層裡面去核對一下。

1. OS 物理層掃盲:是誰吃掉了磁碟?

那麼首先,SSH 登入到你的 CentOS 資料庫伺服器上。我們要先搞明白,到底是不是資料庫自己占的空間。因為很多時候的話,磁碟滿了是因為應用打出來的日誌檔太大了。或者也有可能是系統核心的 Core Dump 檔案搞出來的情況。

執行以下系統級探測命令:

bash 代碼解讀複製代碼# 1. 查看全系統掛載點的使用率,鎖定爆滿的磁碟分割區
df -Th

# 2. 假設資料庫安裝在 /opt/Kingbase/ES/V9 目錄下,我們進入該目錄
cd /opt/Kingbase/ES/V9/data

# 3. 統計資料目錄下各個核心子目錄的物理大小(極其關鍵)
du -sh * | sort -hr | head -n 10

在這裡插入圖片描述

DBA 避坑指南(健康基線法則):看看上面那個真實環境的截圖。也就是說,哪怕你這個資料庫實例啥業務都沒跑,非常健康的情況下,容量吃得最多的永遠是 base 目錄,這個是放實際業務資料的。然後就是 sys_wal 目錄,它是放預寫式日誌的。

這個健康的基線模型要記住。在真實的生產環境裡頭,你看到 base 目錄很大,那是業務資料在漲,這很正常。但是,如果你看到 sys_wal 目錄大得離譜,比如說占了幾十上百GB。那為什麼會這樣呢?通常來說,這說明你的歸檔機制(Archive)失效了。也有可能是有一個極長的交易掛在那邊了,歷史日誌堆著沒法循環覆蓋。這時候你去清理業務資料是沒用的,必須去查系統的歸檔設定。

2. DB 邏輯層全景:實例與資料庫級容量核查

確認了是業務資料,也就是 base 目錄膨脹了以後,我們回到 Win11 本地。接著打開 KStudio 或者 ksql 命令列,進入資料庫的管理視角裡面。

一個資料庫實例下面,通常來說會有好幾個 Database。那麼到底是誰在吃資源呢?金倉資料庫這邊給了幾個很直觀的容量測算函式。執行以下 SQL:

sql 代碼解讀複製代碼SELECT 
    d.datname AS 資料庫名稱, 
    sys_size_pretty(sys_database_size(d.datname)) AS 物理總大小,
    sys_database_size(d.datname) AS 原始位元組數
FROM sys_database d 
ORDER BY 原始位元組數 DESC;

(註:sys_size_pretty 這個函式的話,其實挺好用的。它會自動把那一大串位元組數,給你換成 KB、MB、GB 這種好讀的單位。)

在這裡插入圖片描述

結合上面的監控回饋,我們一眼就能看出來。經過海量業務寫入,那個 test 庫的體積已經飆到了 575 MB,占了資源消耗的榜首,而其他系統級的預設庫才 17 MB。

找到這個大庫之後,還得繼續往下看。因為一個庫裡面可能有成百上千張表。我們必須像拿顯微鏡一樣,把那個真正吃空間的表給找出來。


第二階段:微觀狙擊 —— X 光級表級容量透視與 TOAST 探秘

很多新手只會用簡單的語句去查表的資料量,也就是跑個 count(*)。但是資料列數多,其實不代表占的物理磁碟空間就大。一張表可能只有 10 萬列,但是裡面存了長文字,它的體積往往比一張 1000 萬列但只存純數字的表還要大。

1. 打造「DBA 超級空間掃描器」

所以的話,我給你準備了下面這段企業層級裡的「空間掃描器 SQL」。跑一下,基本上所有表的底層物理資訊就全出來了:

sql 代碼解讀複製代碼SELECT 
    schemaname AS 模式名,
    relname AS 表名,
    sys_size_pretty(sys_relation_size(relid)) AS 純資料大小,
    sys_size_pretty(sys_indexes_size(relid)) AS 索引總大小,
    sys_size_pretty(sys_total_relation_size(relid)) AS 表及附屬總物理大小,
    CAST(sys_indexes_size(relid) AS float) / sys_relation_size(relid) AS 索引資料比
FROM sys_stat_user_tables 
WHERE sys_relation_size(relid) > 0
ORDER BY sys_total_relation_size(relid) DESC 
LIMIT 10;

在這裡插入圖片描述

2. 深度剖析:純資料、索引與 TOAST 機制

看著上面掃描器出來的這個表格,你要去讀懂裡面藏著的那些技術門道。重點看下面這三個東西:

  • 純資料大小(sys_relation_size): 這個的話,就是業務資料在磁碟上實實在在的占位,也就是 Heap Tuple。就像截圖裡紅箭頭指的那個,那個叫 test_bloat 的模擬業務日誌表,純資料就有 558 MB。這就是整個庫體積暴增的元兇了。
  • 索引資料比過高危機: 截圖裡這個表的索引是 0,因為它是一張純日誌流水表。但是如果在真實生產環境裡,你看到一張表的純資料才 1GB,索引大小居然有 5GB。也就是說索引資料比大於 5 了。那為什麼會這樣呢?這說明研發同學在這表上建了一堆沒用的索引。每次寫資料進去,資料庫還得去維護那幾棵龐大的索引樹。這不僅浪費磁碟空間,往往更是把寫入效能拉垮的致命傷。
  • 神秘的差值與 TOAST 機制: 你仔細看的話,有時候會發現純資料大小加上索引大小,還是比總物理大小要小。中間差的那些容量跑哪去了呢?這就得說說金倉底層的 TOAST(超大屬性儲存技術) 了。表裡面要是有了巨大的 JSON 或者是長文字欄位,核心就會自動把這些單列超限的大欄位給切碎壓縮。接著的話,轉移到一張隱蔽的系統附屬表裡面去存著了。

第三階段:核心揭秘 —— 為什麼刪了資料,空間還沒變?

那麼現在,我們就要來看看開頭說的那個讓人崩潰的現象了:明明執行了 DELETE,為什麼空間就是不釋放呢?

1. 撕開 MVCC 的底層面紗

金倉資料庫為了實現企業層級裡的高併發讀寫,也就是讀不阻塞寫,寫不阻塞讀。它在核心裡面用了 MVCC(多版本並發控制) 這個機制。

這個機制是怎麼運作的呢?當你敲 UPDATE 去改一條資料的時候,底層並不是在原來的地方把舊資料抹掉。它是插入了一條全新的資料版本,然後給舊資料打上一個「已過期(Dead Tuple)」的標記。同樣的道理,你敲 DELETE 刪資料的時候,底層也就是給這些資料打了個「被刪除」的隱形標記。物理磁碟上並沒有馬上把它們擦掉。

那些被打上標記的,物理上還占著磁碟空間的資料,在資料庫工程界叫作「表膨脹(Table Bloat)」。這也就解釋了,為什麼你刪了百萬級的資料以後,去 CentOS 上用 du -sh 看底層的物理檔案,大小還是一點都不變的情況。

動手實驗:一鍵復現表膨脹想親自驗證一下的話,你可以在測試庫跑一下這個腳本:先插 50 萬條長文字資料進去,接著 DELETE 刪掉裡面的 30 萬條。這個時候你再去查這個表的物理大小,你會發現空間一點都沒變,這就是經典的表膨脹現場。

2. 檢測表膨脹率:揪出「虛胖」的表

那麼我們需要通過系統統計視圖,來看看一張表到底虛胖到什麼程度了。執行以下 SQL:

sql 代碼解讀複製代碼SELECT 
    relname AS 表名, 
    n_live_tup AS 存活資料列數, 
    n_dead_tup AS 死亡標記資料列數, 
    ROUND(n_dead_tup::numeric / (n_dead_tup + n_live_tup) * 100, 2) AS 虛胖膨脹率百分比
FROM sys_stat_user_tables 
WHERE n_dead_tup + n_live_tup > 0 
ORDER BY 虛胖膨脹率百分比 DESC;

在這裡插入圖片描述

(註:要是你看到某張表的 n_dead_tupn_live_tup 還要大得多,膨脹率都過了 50% 的情況了。這就說明這張表裡的死資料實在太多了。也就是說,它在那白白占著空間,而且的話,你跑全表掃描的時候,效能往往也會掉得很厲害。)

3. 清理多占的空間:常規 VACUUM 與徹底的 VACUUM FULL

那麼既然 DELETE 沒法把空間放出來,我們怎麼才能把這些死掉的資料占的物理空間,真正還給作業系統呢?其實,我們需要用到資料庫裡的一個清理功能,也就是清理命令(VACUUM)

  • 常規清理:VACUUM 表名;這個操作的話,通常來說是比較輕量的。它會去掃一遍全表,然後把你那些死亡資料,也就是 Dead Tuple 占的空間,標記成可以用的狀態。注意啊:它其實還是不會把空間還給作業系統的,物理檔案的大小也不會變小! 那它有啥用呢?它的作用就是,以後要是再有新的 INSERT 資料進來的話,資料庫會優先去用這些空出來的位置。也就是說,它能擋住物理檔案繼續變大。這個操作是不鎖表的,平時經常跑一跑沒問題。
  • 徹底清理:VACUUM FULL 表名;這個才是真正能把空間摳出來,還給 CentOS 作業系統的辦法。那它是怎麼做的呢?底層邏輯其實很簡單的。也就是說,它會去建一個新的物理檔案,接著把活著的那些資料都拷過去,排得緊實一點,最後把原來那個撐大的舊檔案給刪掉。 【高危警告】:VACUUM FULL 的時候,它會給這張表加上最高等級的排他鎖(AccessExclusiveLock)。啥意思呢?就是拷貝完之前,業務那邊想做增刪改查的話,全都會被堵住,根本執行不了!所以說,除非是半夜那種停機維護的時間段,千萬別在業務跑得最猛的時候去敲這個命令!

第四階段:架構調整 —— 用物理隔離和分區機制解決長遠問題

一直靠 VACUUM FULL 去處理問題,這其實也就是維運層面沒辦法的辦法。那麼,如果你是有架構思維的 DBA 的話,我們往往就需要去規劃一下底層的東西了。也就是說,要從根本上把一個地方容量不夠,還有清理的時候鎖表這些痛點給解決掉。

1. 物理層空間轉移:用表空間(Tablespace)來搞定

比如說你的 /opt 所在的系統碟,容量已經到 99% 了。甚至於你想跑個 VACUUM FULL,連要用的暫存交換空間都擠不出來了。那遇到這種情況怎麼辦呢?

這個時候的話,我們可以在 CentOS 上掛一塊新的 2TB 資料碟上去,比如掛到 /data_new。接著,我們用金倉資料庫裡面的表空間(Tablespace)功能,在業務不用停的情況下,把那些很大的歷史表,挪到新的磁碟裡面去:

sql 代碼解讀複製代碼-- 1. 在新磁碟路徑下建立一個名為 ts_archive 的物理表空間
CREATE TABLESPACE ts_archive LOCATION '/data_new/kingbase_ts';

-- 2. 將龐大的歷史表,從預設的滿載磁碟,動態挪移到新的表空間(新磁碟)中
ALTER TABLE trade_history_log SET TABLESPACE ts_archive;

敲了這條指令以後,核心就會直接把這張表的物理檔案,全都搬到 /data_new 底下去了。那麼這樣的話,原來那個快滿的系統碟,也就不用那麼吃緊了。

2. 邏輯架構調整:從 DELETE 到表分區(Partitioning)

對於系統日誌表、訂單流水表這種帶著明顯時間屬性,每天瘋狂寫入的資料。真的不要再寫定時任務去 DELETE 一個月前的資料了!因為 MVCC 機制的存在,高頻的大批量 DELETE 簡直就是自己給自己找麻煩。

企業層級裡的終極解決方案是:時間範圍分區表(Range Partition)。

也就是說,在建表初期,我們就按照月份,把一張龐大的邏輯表,在物理底層切成 12 張獨立的子表,就像 log_2025_01, log_2025_02 那樣。

當 1 月份的資料過期了需要清理的時候,我們不再去執行 DELETE 了。而是直接敲一條很輕量的 DDL 命令:

sql 代碼解讀複製代碼DROP TABLE log_2025_01;

這種做法的好處在於: DROP TABLE 根本不走 MVCC 機制,也就不會產生什麼死亡標記。它在 Linux 底層直接就把那個物理檔案給抹掉了,也就是 unlink。100GB 的空間,0.1 秒內瞬間就釋放還給作業系統了。一點表膨脹都不產生,而且也不會影響目前 2 月份子表的寫入!

結語

這篇文章,其實走完了一個挺完整的過程。我們先是在 CentOS 上跑 du -sh 去看物理磁碟的情況。接著用 sys_total_relation_size 去摸清邏輯層面的占用。然後還扒開了 MVCC 底下那個讓人頭痛的「表膨脹」機制。最後呢,又用上了表空間騰挪、分區表這些手段,把整體架構往上提了一下。磁碟空間這事兒,算是實打實地處理了一回。

搞技術的,我們不光要知道怎麼把空間清出來,這只是第一步。更關鍵的是,得搞明白「空間它到底為什麼沒了」,把這個根兒給挖出來。然後,再往前走一步的話,就是通過事先的架構設計,把這些要命的容量問題,在它剛冒出點苗頭的時候就直接按下去。

你想想看,到了這會兒,像連線數爆炸、鎖衝突卡頓、磁碟空間告警這些情況,你都能應付得來。其實這已經算是過了那個只能被動去救火的階段了。但是,在系統更底層的地方,有時候還會藏著一些那種情況。就是平時跑得好好的,偶然給你慢一下,慢得讓人摸不著頭腦的詭異 SQL。

所以,在下一篇裡,咱們這個系列專欄就要進入到最後的部分了,也就是——「獵捕慢查詢:執行計畫的微觀解析與索引調優實戰」。我會跟你一塊兒,去琢磨那些看起來很複雜的「執行樹」到底是怎麼回事。然後把資料庫的效能瓶頸給它控制住。行,那咱們下一篇見吧!


原文出處:https://juejin.cn/post/7652513657561792563


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   ❤️1
436
🥈
我愛JS
📝1  
13
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登