WordPress 資料庫清理優化——瘦身完整指南

網站後台變慢、備份檔越來越大、搬家時光是匯出資料庫就要等上好幾分鐘,很多人第一時間想到的是換主機或裝快取外掛,卻忽略了真正拖住效能的往往是那個沒人去看的 WordPress 資料庫。內容是有意義的資產,但跟內容一起膨脹的,還有一堆沒人用得到的東西:每次按儲存就多一份的文章修訂版本、過期卻沒被清掉的暫存資料、外掛移除後留下的孤立資料,以及那張幾乎每個頁面都要讀一次的 wp_options。

WordPress 資料庫清理的目標不是把資料砍光,而是分辨「哪些是內容、哪些是廢料」,把廢料安全地清掉,再把佔住空間的資料表壓縮回來。這篇會把暫存、修訂版本、孤立 postmeta 跟孤立資料表逐一拆開,說明怎麼判斷、怎麼清、用哪種方式清最安全,並且把 WooCommerce 商店的特殊情況一併講清楚。動手之前提醒一句:每一段刪除動作之前都要有可還原的備份,這點後面會單獨講。

WordPress 資料庫為什麼會越來越肥

資料庫變肥的原因可以分成兩類:一類是你刻意產生的內容,文章、頁面、商品、留言;另一類是系統與外掛日積月累留下的廢料。前者該留,後者才是清理的對象。一個跑了三年以上的網站,廢料的體積有時候會超過真正的內容。

WordPress 預設的運作機制會在好幾個地方持續堆積資料。每按一次「儲存草稿」或「更新」,系統就在 wp_posts 建立一份完整的修訂版本,預設沒有上限;外掛用來暫存 API 回應與授權檢查結果的 transient,到期後不會主動被刪除,要等到下次有人讀取才清掉,低流量網站因此會無限累積;文章被永久刪除時,wp_posts 的那一列會消失,但 wp_postmeta 裡對應的中介資料不一定會跟著清乾淨,於是留下指向不存在文章的孤立列。

除此之外,每按一次「新增文章」就產生一筆自動草稿,沒發布就離開的話它會留著;丟到垃圾桶的文章預設停留 30 天才自動刪除;過濾掉的垃圾留言若沒定期清,也會累積成上萬筆。這些東西單看都不大,但乘上時間就成了拖慢每一次查詢、撐大每一次備份的負擔。

動手清理前一定要先做的資料庫備份

任何刪除或優化動作之前,先做一份可以還原的完整資料庫備份,這是整個流程裡唯一不能省的步驟。清理用的 SQL 一旦 WHERE 條件寫錯,或外掛批次刪除時誤判,影響的是正式環境的真實資料,沒有備份就只能接受損失。

備份方式可以擇一:用主機商後台提供的資料庫匯出或快照功能、用 phpMyAdmin 的匯出把整個資料庫存成 .sql 檔,或用 UpdraftPlus、BackWPup 這類備份外掛建立排程備份。重點不是用哪一個,而是備份檔要存在跟網站不同的地方,例如雲端空間或本機,避免主機本身出事時備份跟著陪葬。

備份完還有一個常被跳過的動作:確認備份能還原。一份匯不回去的備份等於沒有備份。如果是要對 wp_postmeta、wp_options 這種核心資料表動手,建議在還原測試環境或本機先跑一次,確定流程通了,再回到正式站操作。

文章修訂版本怎麼清、怎麼設上限

修訂版本是最容易膨脹、也最容易處理的一塊。一篇被編輯過兩百次的文章,就有兩百份修訂版本各自佔著 wp_posts 與 wp_postmeta 的列;在 WooCommerce 商店裡,商品同樣會產生修訂版本。

清理分成兩件事:先設上限止血,再清掉既有的多餘版本。止血的做法是打開網站根目錄的 wp-config.php,在 /* That's all, stop editing! */ 這行註解之前加上設定:

define( 'WP_POST_REVISIONS', 5 );
define( 'AUTOSAVE_INTERVAL', 300 );

第一行把每篇文章的修訂版本上限定在 5 份,WordPress 會保留最新的 5 份、把更舊的推掉;第二行把自動儲存的間隔拉長到 300 秒,減少自動草稿的產生頻率。數字可以依需求調整,設成 false 可以完全關閉修訂功能,但不建議——修訂版本是內容寫壞時的救命還原點,留個 5 到 10 份是合理的平衡。設定上限是預防膨脹最有效的單一動作。

既有的多餘版本可以用外掛一鍵清除,WP-Optimize、WP-Sweep 都做得到。偏好命令列的人用 WP-CLI 更乾淨,先數再刪:

wp post list --post_type=revision --format=count
wp post delete $(wp post list --post_type=revision --format=ids) --force

如果站上有幾萬筆修訂版本,一次刪可能撞到 PHP 記憶體上限,改成分批,每次處理 500 筆,重複跑到歸零為止:

wp post list --post_type=revision --format=ids --posts_per_page=500 | xargs wp post delete --force

過期暫存資料的清理與物件快取的影響

暫存資料就是前面提到的 transient,存放在 wp_options 裡的臨時鍵值對,外掛拿它來快取 API 回應、授權檢查與運算結果。問題出在過期的 transient 不會被主動清除,得等到有程式去讀它才會被刪掉,所以裝了很多外掛又流量不高的網站,過期暫存資料會一直疊上去。

先查一下到底累積了多少筆過期暫存,再決定要不要清:

SELECT COUNT(*) FROM wp_options
WHERE option_name LIKE '_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP();

清除最簡單的方式是 WP-CLI 的一行指令,它只刪過期的、不會動到還在有效期內的暫存:

wp transient delete --expired

要用 SQL 手動清也可以,但要連 _transient_ 與對應的 _transient_timeout_ 兩種列一起刪,否則會留下半截資料:

DELETE a, b FROM wp_options a
INNER JOIN wp_options b ON b.option_name = REPLACE(a.option_name, '_transient_timeout_', '_transient_')
WHERE a.option_name LIKE '_transient_timeout_%'
AND a.option_value < UNIX_TIMESTAMP();

這裡有一個多數教學沒講清楚的關鍵:如果網站已經啟用了 Redis 或 Memcached 之類的物件快取後端,transient 其實是存在快取後端、而不是 wp_options,過期項目會由快取的存活時間機制自動處理。這種情況下,去清 wp_options 裡的暫存資料是做白工,該檢查的是物件快取的設定而不是資料表。先確認自己的 transient 到底存在哪裡,再決定清理對象。

孤立 postmeta 與真正的孤立資料表怎麼分辨與移除

「孤立資料」常被混為一談,其實要分成兩個層次:孤立的資料列,以及孤立的整張資料表,兩者的判斷與清理方式完全不同。

孤立 postmeta 指的是 wp_postmeta 裡那些 post_id 指向已不存在文章的列。文章被永久刪除後,外掛存的自訂欄位特別容易留下這種孤兒。先用查詢算出有多少筆:

SELECT COUNT(*) FROM wp_postmeta
WHERE post_id NOT IN (SELECT ID FROM wp_posts);

數量明顯時再刪除,刪之前再次確認備份就緒:

DELETE FROM wp_postmeta
WHERE post_id NOT IN (SELECT ID FROM wp_posts);

孤立資料表是另一回事。很多外掛安裝時會在資料庫建立自己的資料表,但移除外掛時不一定會把這些表清掉,於是留下一張張沒有任何現役外掛在用的「孤兒表」。WordPress 標準預設只有 12 張核心資料表(早期版本是 11 張,多出來的是 wp_termmeta),名稱以 wp_ 開頭、像 wp_posts、wp_options、wp_users 這類;超出這個範圍、又對得上某個已移除外掛命名習慣的表,很可能就是孤兒。

辨認孤立資料表要靠人工判斷,沒有一鍵安全的自動工具,因為某些表名雖然陌生,卻是現役外掛正在使用的。可行的流程是:在 phpMyAdmin 列出所有資料表,把名稱對照目前還在啟用的外掛清單,找出對不上任何外掛、又確定該外掛早已移除的表,先把它匯出存成單獨的備份檔,再 DROP 掉。匯出那一步是給自己留後路——萬一判斷錯了,還能把表還原回去。沒把握的表寧可留著,也不要冒險刪。

wp_options 與 autoload 資料的瘦身

wp_options 是最值得花時間的一張表,因為它有個 autoload 欄位,值為 yes 的選項會在每一個頁面載入時被一次讀進記憶體。當太多非必要的設定被標成 autoload,伺服器每次回應請求都得多扛一份不必要的負擔,這往往是首頁初次載入特別慢的隱形原因。

先找出是誰塞了最多 autoload 資料,列出佔空間前幾名:

SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size DESC
LIMIT 20;

跑完會看到一份清單。從 option_name 通常能判斷出是哪個外掛或主題留下的,接著問自己一個問題:這個選項真的需要每個頁面都載入嗎?如果某個設定只在後台特定頁面用得到,可以把它的 autoload 值從 yes 改成 no,讓 WordPress 只在明確呼叫時才去讀,藉此減輕每次頁面載入的負擔。

改之前要做足功課,亂改 autoload 可能讓外掛功能失常。比較穩的起手式是先處理那些已經停用、卻還在大量 autoload 的外掛留存資料;至於現役外掛的設定,除非確定用途,否則不要動。較新版本的 WordPress 已經會在 autoload 資料超過門檻時自動把部分選項改為不自動載入,但這套自動機制不會幫你清掉早期外掛留下的歷史包袱,人工檢查仍有價值。

自動草稿、垃圾桶與垃圾留言的批次清理

這三類資料屬於「系統會自己清、但常常沒清成」的情況。WordPress 內建機制會清掉超過 7 天的自動草稿,垃圾桶裡的文章也會在預設 30 天後自動刪除,這兩件事都掛在 WP-Cron 的排程事件上。問題是,一旦 WP-Cron 被停用或被擋住,這些自動清理就不會跑,資料便無限期留著。

可以用 WP-CLI 直接把它們清掉。刪自動草稿與清空垃圾桶:

wp post delete $(wp post list --post_type=any --post_status=auto-draft --format=ids) --force
wp post delete $(wp post list --post_type=any --post_status=trash --format=ids) --force

垃圾留言累積起來同樣會拖慢留言相關查詢,一併清掉:

wp comment delete $(wp comment list --status=spam --format=ids) --force

如果發現這幾類資料一直清不乾淨、過陣子又長回來,根因通常在 WP-Cron 沒在跑。檢查排程事件還在不在,並確認 wp-config.php 沒有把 DISABLE_WP_CRON 設成停用、外部也連得到 wp-cron.php,把排程修好才是治本。

OPTIMIZE TABLE 與資料表碎片整理

刪掉資料只是把列移走,磁碟上的資料表檔案不會自動縮小。InnoDB 資料表隨著列被新增、更新、刪除會逐漸碎片化,檔案撐大了卻沒在刪除後縮回,這些被釋放卻沒回收的空間會多吃磁碟與緩衝池記憶體。OPTIMIZE TABLE 的作用就是回收這些空間、把資料檔重新整理緊湊。

先看哪幾張表有可回收的空間,DATA_FREE 數字大的就是整理對象:

SELECT TABLE_NAME,
ROUND(DATA_LENGTH / 1024 / 1024, 2) AS data_mb,
ROUND(DATA_FREE / 1024 / 1024, 2) AS free_mb
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND DATA_FREE > 0
ORDER BY DATA_FREE DESC;

針對佔最多空閒空間的表逐一整理,通常 wp_posts、wp_postmeta、wp_options 是常客:

OPTIMIZE TABLE wp_posts;
OPTIMIZE TABLE wp_postmeta;
OPTIMIZE TABLE wp_options;

WP-CLI 也提供一行指令一次整理整個資料庫:wp db optimize。要提醒的是時機:在 MySQL 8.0 與 MariaDB 10.6 之後,這個操作採用線上 DDL,不會鎖住資料表的讀寫;舊版本則可能短暫鎖表。即使是新版不鎖表,整理過程仍會大量消耗磁碟 I/O,可能讓查詢延遲暫時升高,所以正式站務必挑流量低的時段執行。整理前後各跑一次上面那段查詢,對照 free_mb 的變化,就能量化這次清理到底回收了多少空間。

外掛、WP-CLI、phpMyAdmin 三種清理方式怎麼選

前面每一步幾乎都同時給了外掛、WP-CLI、SQL 三種做法,但它們適合的人不一樣,選錯方式不是效率差,而是風險差。

外掛適合不想碰命令列、也不熟 SQL 的一般站長。WP-Optimize 介面直覺,修訂版本、自動草稿、垃圾留言、過期暫存、資料表優化都能勾選處理;Advanced Database Cleaner 擅長揪出孤立資料與不明的選項;UpdraftPlus 與 BackWPup 則專注在備份與還原。實務上常見的組合是清理外掛搭配備份外掛一起用,而不是指望單一外掛包辦全部。要注意外掛在背後一樣是呼叫 OPTIMIZE TABLE 與批次刪除,正式站要在低流量時段跑。

WP-CLI 適合有 SSH 權限、要把清理寫進排程或部署流程的開發者與站務人員。它不開瀏覽器、可非互動執行、能精準分批,是大型網站最安全的批次清理方式,前面那些 wp post、wp transient、wp db optimize 指令串起來就是一份完整腳本。

phpMyAdmin 直接下 SQL 適合需要精準控制、處理外掛與 WP-CLI 都搆不到的特殊情況,例如辨認與 DROP 孤立資料表、針對特定 meta_key 清理。代價是門檻最高、出錯影響最直接——一個寫錯的 WHERE 條件就可能清掉整張表,所以每次執行前先用 SELECT 把要刪的範圍撈出來確認,沒問題再換成 DELETE。

WooCommerce 商店清理資料庫要注意什麼

WooCommerce 商店的資料庫清理有一個一般部落格沒有的變數:訂單資料的儲存位置。較新版本的 WooCommerce 預設啟用了高效能訂單儲存(HPOS),訂單資料改放在 wp_wc_orders、wp_wc_orders_meta 這類專屬資料表,而不是傳統的 wp_posts 與 wp_postmeta。

這會直接影響前面教的孤立資料清理。如果商店已經啟用 HPOS,那段用 post_id NOT IN (SELECT ID FROM wp_posts) 找孤立 postmeta 的查詢就無法涵蓋訂單相關的資料,貿然執行還可能誤判。處理 WooCommerce 商店的孤立資料時,要同時考慮傳統儲存與 HPOS 兩套系統,先確認自己的商店用的是哪一種,再決定查詢條件。

商品同樣會產生修訂版本,所以 WP_POST_REVISIONS 的上限設定對商店一樣有效,能明顯壓低商品編輯頻繁時的膨脹。至於訂單、發票這類涉及金流與交易紀錄的資料,性質跟修訂版本、暫存完全不同,它們是必須長期保存的營運與帳務資料,清理資料庫時要明確劃出界線,不要把交易紀錄當成廢料一起清掉。對保存年限有疑慮時,以稅務與帳務的保存規定為準。

清理之後,資料庫怎麼維持在精實狀態

把廢料清完、資料表壓縮回來之後,最容易犯的錯是以為一勞永逸。資料庫的廢料是會復發的——沒有設修訂版本上限、沒有固定的維護節奏,幾個月後它就會長回原本臃腫的樣子。清理只完成了一半,上限與排程才是另一半。

合理的節奏是這樣:永久性的,在 wp-config.php 設好 WP_POST_REVISIONS 上限,這是最划算的一次性投資;每月跑一次清理腳本或外掛,處理修訂版本、過期暫存與垃圾留言,並回頭看看孤立 postmeta 有沒有又長出來;每次做完大量匯入、網站搬家或移除外掛之後,立刻針對受影響的資料表清孤立資料並重跑 OPTIMIZE TABLE。

最後要分清楚一件事:資料庫清理能讓備份變小、搬家變快、查詢少背一點包袱,但它不是查詢優化的替代品。如果資料庫本身不大卻還是慢,問題出在缺索引、N+1 查詢或寫得差的外掛程式碼,那要從查詢與索引下手,清廢料幫不上忙。把清理、上限、排程三件事固定下來,再搭配物件快取與該補的索引,資料庫才會長期穩定地待在精實、好查、好備份的狀態。

相關文章
標籤: WP-CLI, WordPress 資料庫清理, 資料庫優化, 文章修訂版本, transient 暫存