WP-CLI 搬家教學:資料庫遷移 4 步驟一次搞懂

多數人對「搬 WordPress 網站」的印象,是裝一個搬家外掛、按幾個按鈕、盯著進度條跑完。這條路在小站上確實好用,可是資料庫一大、主機又卡著執行時間上限,外掛常常匯出到一半就逾時、上傳檔案超過限制被擋在門外,進度條停在半路動也不動,最後只能重跑、拆檔案、換一個外掛再試一次。

然而真正熟後台的人,遇到大站反而回頭用最陽春的命令列。幾行 wp db exportwp search-replace 打完就收工,比等外掛跑完還快、還穩。差別不在工具新舊,而在敢不敢打開終端機。一旦繞過瀏覽器這一端,主機能承受多大的資料庫,你的搬家就能吃多大。

這篇要講的,就是用 WP-CLI 搬家指令做資料庫遷移這條路怎麼走:從匯出、搬檔、匯入,到最關鍵的用 search-replace 換網址,一步一步拆清楚它穩在哪、又有哪些坑一定得先繞開。先從它跟外掛搬家到底差在哪講起。

WP-CLI 搬家跟外掛搬家,有什麼差異?

先把觀念定下來,不急著給指令。WP-CLI(WordPress Command Line Interface)是 WordPress 官方維護的命令列工具,讓你透過終端機直接對資料庫和檔案下指令,不經過任何網頁介面。它讀的是 wp-config.php 裡那組資料庫連線憑證,等於站在主機這一端,直接跟資料庫對話。

圖形化搬家外掛跟它的本質差異,在於「請求是從哪裡發出的」。外掛得把整個資料庫包成一個檔案,透過瀏覽器上傳或下載。這一趟就得受主機三個設定管:單檔上傳大小上限、單次請求的執行時間上限、還有 PHP 記憶體上限。資料庫肥一點,包出來的檔案動輒好幾百 MB,很容易在其中一關撞牆。你會看到的畫面就是那條卡住的進度條,本質上是瀏覽器這一端的請求先被主機掐斷了。

WP-CLI 走的是另一條路。指令直接在主機端執行,沒有網頁請求,自然也沒有網頁請求的逾時限制。資料庫再大,也就是一行指令跑下去、等它跑完,不會卡在某個瀏覽器分頁那端轉圈圈。這也是為什麼熟手遇到肥大網站會直接切到終端機。不是命令列比較潮,是它剛好躲開了外掛最容易死的那幾個限制。

搬家外掛經瀏覽器上傳下載會撞上主機上傳大小、執行逾時、記憶體三道限制卡住進度條,WP-CLI 直接對主機下指令不受這些限制
WP-CLI 繞過了外掛卡住的那幾個關卡,因為它不經瀏覽器、沒有網頁逾時限制。

也順帶一提,WP-CLI 是個持續在擴充的工具,指令生態一直在長,官方近年甚至往「讓 AI 代理能直接操作」的方向補指令。不過那些是後話,這篇只聚焦資料庫遷移這塊最實用的核心指令。

那什麼情況才真的用得上這套方法?前提是你的主機要開放 SSH 存取權限,讓你能登進終端機下指令;操作者本身也得是開發者,或至少對工程操作不陌生。如果你租的是連 SSH 都不給的陽春共享主機,或者從沒碰過終端機、手上又沒有寬裕的時間可以慢慢試,這套方法就未必適合你。這點文章最後會再誠實講清楚。

動手前要準備什麼?

正式敲指令之前,先花幾分鐘把底下這幾件事確認過,能省掉做到一半才發現卡關、又得整組重來的麻煩。

第一件是權限。你得對舊主機和新主機都有 SSH 存取權限,兩端都能登進去下指令才有辦法操作。這一步沒過,後面全都免談,所以擺在最前面。

第二件是確認新主機裝好了 WP-CLI。多數主流主機商的方案都已經內建,你只要 SSH 登進去、切到網站目錄,敲一行就能查:

wp --infoCode language: Bash (bash)

跑得出版本資訊,代表工具在、環境也對;如果它回報找不到指令,就照官方安裝指南把 WP-CLI 裝上再繼續。

第三件是新舊資料庫的連線資訊。這幾個值都寫在各自的 wp-config.php 裡,也就是 DB_NAME(資料庫名稱)、DB_USER(帳號)、DB_PASSWORD(密碼)。大多數指令會自動讀這份設定,你不必手動帶;但先確認過新主機那份是對的、資料庫確實連得上,能避免匯入時才發現連線資訊填錯。

最後,也是最重要的一件,就是動手前一定要先手動備份一份。其實 wp db export 匯出的那個 SQL 檔本身就是最快的備份,但別只留在主機上;建議額外複製一份到主機以外的地方,例如你自己的電腦或另一個儲存空間。搬家過程中,舊主機那份原始資料在整套流程確認無誤之前都不要刪,讓你隨時有退路可以重來。備份放好、不邊做邊刪,是這整篇最不該省的一步。

步驟一:把舊資料庫完整匯出成一個檔案

第一步要做的,是把舊站整個資料庫打包成一份 SQL 檔。這裡先講清楚它匯出的是什麼:只有資料庫,也就是你的文章、設定、選單、外掛資料這些存在資料表裡的東西,不含 wp-content 底下那些實體檔案(圖片、主題、外掛程式本身)。那些檔案得另外用一般的檔案傳輸方式搬,不在這份 SQL 裡。這份檔案的用途,就是等一下要在新主機原封不動地灌回去。

最陽春的用法是直接下 wp db export,不給任何檔名。這時候它會自動產生一個檔名,格式是 {資料庫名稱}-{年-月-日}-{一串隨機亂碼}.sql。那串亂碼不是隨便加的,是刻意設計。如果每次都用固定好猜的檔名(例如 backup.sql),這份含有整站資料的檔案就等於把後門地址寫在牆上,別人能直接猜到路徑把它抓走。加上隨機亂碼,就是讓這份備份檔的位置不容易被外人猜中。

你也可以自己指定檔名,方便辨識是哪次搬的。資料庫特別大的話,還能在匯出時先做減法,就是用 --tables 只匯出指定的幾張表,或用 --exclude_tables 把不必要的表排除掉。有些外掛會留下大量暫存資料或存取紀錄表,這類東西搬過去也用不上,排掉能明顯縮小要搬的檔案體積、也讓後面每一步都跑得更快。

還有一個安全習慣值得養成。SQL 檔匯出後,如果它落在網站根目錄這種能被瀏覽器公開存取的位置,記得先把它搬到網站目錄以外、外人抓不到的地方,再繼續下一步。一份完整的資料庫匯出檔攤在公開路徑上,跟把保險箱放在門口沒兩樣。

匯出指令怎麼下?

實際的指令長這樣。最單純的完整匯出,讓它自動命名:

wp db exportCode language: Bash (bash)

想自己指定檔名,就把檔名接在後面:

wp db export my-site-backup.sqlCode language: Bash (bash)

如果只想搬特定幾張表,或想把某些暫存、紀錄表排除在外,就加上對應參數(表名之間用逗號隔開):

wp db export --exclude_tables=wp_actionscheduler_logs,wp_options_transientCode language: Bash (bash)

匯出跑完,你會在當前目錄看到那份 SQL 檔。確認它產出來、體積也合理之後,記得依前面說的,把它移到能被公開存取的網站目錄之外,再進下一步。

步驟二:把檔案搬到新主機

檔案匯出好了,接著要把它從舊主機送到新主機。怎麼搬,看你的情境。

如果新舊站在同一台主機、只是不同的網站目錄,那最簡單,直接用主機端的複製貼上把 SQL 檔搬過去就好,不必大費周章。

跨主機的情況才需要動用傳輸指令。不管新主機是另一台共享主機還是遠端的 VPS,都可以用 scprsync 透過 SSH 直接把檔案從舊主機傳到新主機。這兩個都是 Linux 系統通用的檔案傳輸指令,用法是把來源檔案和目的地位址寫在一起。這裡有個常被忽略的省事關鍵:兩台主機之間可以直接傳,不必先把檔案下載回你自己的電腦、再從電腦上傳到新主機。那等於多繞一趟、還多耗一次你家的頻寬。

如果你想更進一步,而且新舊主機都能跑 WP-CLI,其實可以善用它的 --ssh= 全域參數。這個參數能讓你在自己的機器上,對遠端主機直接下 WP-CLI 指令,格式大致是 使用者@主機:路徑。搭配這個參數,甚至能把匯出跟匯入用管線串成一行。概念上就是讓一端 wp db export 的輸出,直接餵給另一端的 wp db import,中間完全不落地成檔案。

不過管線化雖然帥,對第一次操作的人未必是好選擇。中間不落地,代表萬一哪個環節出錯,你很難知道是匯出壞了、傳輸斷了,還是匯入沒吃進去。所以初次搬家,建議還是老實分成兩步驟做:先匯出、傳過去、確認檔案在、體積對,再匯入。每一步都留下可檢查的中繼檔案,出事時好回頭抓問題。

最後一樣是安全收尾。檔案順利傳到新主機之後,記得回頭把舊主機上那份暫存的 SQL 檔刪掉,別讓一份完整的資料庫匯出檔留在網站根目錄裡養灰塵。

步驟三:把資料庫灌進新主機

檔案到位,就用 wp db import 把它灌進新主機的資料庫。指令本身很單純,把 SQL 檔名接在後面即可:

wp db import my-site-backup.sqlCode language: Bash (bash)

它會讀新主機 wp-config.php 裡那組 DB_HOSTDB_NAMEDB_USERDB_PASSWORD 憑證,把 SQL 檔內容匯進對應的資料庫。

匯入前有件事一定要先確認,那就是新主機那頭得先有一個乾淨、空的資料庫在等著。實務上通常是先在新主機裝一套全新的 WordPress(它會順帶建好資料庫),或至少手動把資料庫建起來。如果連資料庫都還沒有,可以用同一系列的 wp db create 先依 wp-config.php 的設定建立一個,再回來匯入。這一步的重點在於,import 是把資料倒進既有的資料庫,不是幫你無中生有變一個出來。

跟外掛最不一樣的體感,就在你按下 Enter 之後。這裡不會有進度條,畫面就是安安靜靜地等,資料庫大,它就跑久一點;跑完,終端機會回一句類似 Success: Imported from 'my-site-backup.sql'. 的訊息,代表匯入成功;要是中途出錯,它也會直接把錯誤訊息印出來給你看。你要盯的是這行回報,而不是一條會轉的進度條。也正因為這一步跑在主機端、沒有網頁逾時這回事,資料庫再大也只是等指令跑完,不會像外掛那樣卡在畫面轉圈圈。這正是開頭說的,命令列穩在哪。

步驟四:用 search-replace 把網址換成新的

資料庫灌進去了,但網站現在還不能用,因為裡頭成千上萬筆資料,全都還寫著舊網址。搬家最容易翻車、也最考驗你懂不懂資料庫的,就是換網址這一步。這裡先講一個很多人踩過的雷,就是不能直接拿資料庫工具的「文字取代」功能,把舊網址一次換成新網址。

原因藏在 WordPress 存資料的方式裡。它把很多設定,例如外掛選項、選單、頁面編輯器排版資料等等,不是存成單純的文字,而是存成一種叫「PHP 序列化字串」的格式。這種字串的特別之處在於,它會把「這段文字有幾個字」這個長度數字一起記進去。舉個示意的例子:一段序列化資料存的若是網址 http://old.com(14 個字元),字串裡就會夾帶一個「14」的長度記錄。

問題就出在這裡。如果你只是粗暴地把 old.com 換成更長的 newsite.com,網址文字是換掉了,可是那個「14」的長度記錄沒跟著更新。等 WordPress 之後要讀回這筆資料,它會照記錄去數 14 個字,發現對不上、讀不回來,這整組設定就壞了。輕則畫面跑出一堆亂碼,重則整筆設定直接遺失。一般的文字取代工具、陽春的 SQL 取代指令,全都只會換字、不會動那個長度記錄,這也是它們會把網站搞壞的根本原因。

wp search-replace 聰明就聰明在這裡。它換網址時不是無腦替換,而是會先讀懂這段序列化資料的結構,把字串換掉之後,重新算過新的長度、再把正確的長度記錄寫回去。換句話說,它換完之後那個「14」會自動被改成新網址對應的正確數字,資料還是讀得回來、設定不會壞。這件事,一般的文字編輯器和陽春的資料庫取代工具都做不到。這也正是「換網址一定要用這個指令、而不是隨便找個工具改」的理由。

粗暴文字取代只換網址字串、長度記錄沒更新導致資料讀不回來,wp search-replace 會同步更新序列化字串的長度記錄讓資料正常讀回
不是隨便找工具換字,而是要用會重算序列化長度記錄的 wp search-replace,資料才不會整個壞掉。

下指令前一定要先跑一次乾跑模式

知道要用對指令還不夠,正式換之前,先養成一個保命習慣,就是加上 --dry-run 參數,讓它空跑一次。

wp search-replace 'http://old-site.com' 'https://new-site.com' --dry-runCode language: Bash (bash)

乾跑的意思是,它會把整套搜尋取代完整走一遍、然後印出一張報表,但完全不會動到真正的資料庫。報表會告訴你:這次動到哪幾張表、每張表預計取代幾筆。你要做的,是看這些數字兜不兜得起來。如果你心裡大概知道舊網址在站上出現的規模,報表的數字跟這個預期差不多,才代表搜尋字串下對了,可以正式執行。

報表裡還有一個欄位值得看,每筆取代都會標一個處理方式,是走 PHP 還是走 SQL。簡單說,含有序列化資料、需要重算長度的欄位,會由 PHP 逐筆處理(也就是前面說的那套聰明算法在運作);單純的文字欄位則用 SQL 直接換。你不用去記哪個一定對應哪個,重點是知道報表在告訴你「這批資料是用哪種方式在處理」,心裡有個底。

這一步真的不能省。萬一報表的數字跟你預期差很多,例如預計換幾百筆、結果報表說幾萬筆,或反過來一筆都沒有,先別急著把 --dry-run 拿掉照跑。回頭檢查搜尋字串是不是打錯了:協定寫成 http 還是 https、網址結尾有沒有多一條斜線、有沒有打錯字。先把字串確認對,再正式執行,遠比換壞了才回頭救省事。

哪些欄位要跳過不要動?

正式下指令時,行家還會多加一個常被忽略、但很值得養成的習慣,就是加上 --skip-columns=guid,把 guid 這個欄位跳過不換。

wp search-replace 'http://old-site.com' 'https://new-site.com' --skip-columns=guidCode language: Bash (bash)

guid 是每篇文章的永久識別碼。它雖然長得像網址,但設計上就不該隨搬家更動。它的角色是給 RSS 訂閱之類的機制拿來認「這篇是不是同一篇文章」的固定標記。如果換網址時連 guid 一起改了,訂閱端可能會把舊文章誤判成新文章、重新推播一遍。要講清楚的是,這個影響其實輕微,主要就是 RSS 讀取紀錄那一塊,不是站毀等級的災情;但既然跳過它幾乎沒成本、又能避開這個小麻煩,加上這個參數就成了熟手的預設動作。

同樣道理,如果有些表你確定不想動,例如某些外掛存授權資訊、或存著跟網址無關內容的表,可以用 --skip-tables 把它們排除在取代範圍之外(多張表用逗號隔開)。取代只碰真正需要換網址的地方,動得越精準,出意外的機會越小。

遇到多站點或超大型資料庫,指令要怎麼調整?

前面四步是標準流程。但如果你的站不是單一網站,或資料庫大到不尋常,search-replace 還得再調整一下。這類進階情境集中收在這節,免得前面的主線被岔題打斷。

第一種是多站點(Multisite),也就是一套 WordPress 底下跑好幾個子站的架構。這裡有個容易漏掉的預設行為,就是 search-replace 預設只處理「你目前所在的這個子站」的資料表,其他子站它一概不碰。所以如果你要一次把整個網路所有子站的網址都換掉,得加上 --network,讓它涵蓋全部已註冊的子站表;反過來,如果你只想針對某一個特定子站動手,就用 --url= 指定那個子站的網址。搞錯這個,很容易發生「換完發現只有一個子站生效、其他還是舊網址」的狀況。

第二種是超大型資料庫,動輒幾十萬筆以上的那種。逐筆去讀、去重算序列化資料是要花時間的,資料量一大就會明顯拖慢。這時候可以做的取捨是縮小範圍,用 --skip-columns 把確定不含網址的欄位排掉,或針對特定表分開跑,例如只跑存文章的 wp_posts、存文章附加資料的 wp_postmeta,避開那些根本不會有網址的表,就能縮短單次執行的時間。

至於 --precise 這個參數,是要它對所有欄位都改用最嚴謹的逐筆方式處理。它更周全,代價是會明顯變慢,所以不是每次都要開。務實的做法,是先判斷自己資料庫的實際大小和內容,資料量普通就用預設方式跑,真的碰到需要對全部欄位都上最嚴格處理、又能接受它慢下來,才把 --precise 加上去。不必為了求安心一律開到最嚴格,那往往只是白白多等。

這個方法不是每個情況都適合

把命令列講得這麼穩,最後還是得誠實補一句:它不是萬用解,也不是每個人、每個站都適合自己動手搬。與其硬上出事,不如先評估清楚。

最直接的門檻是主機權限。這整套方法的前提是你能 SSH 登進主機下指令,可是很多便宜的共享主機根本不開放 SSH。連終端機的門都進不去,這篇教的指令一行都跑不了,那就別勉強,改用主機商提供的其他搬家方式。

再來是操作經驗。如果你對終端機完全陌生、手上又沒有寬裕的時間可以慢慢查、慢慢試錯,那在正式站上邊查邊做的風險並不低。一個指令下錯、又沒有好好備份,救回來的成本可能遠高於一開始就找人幫忙。

還有一種是資料本身的風險等級。如果這個站牽涉到付費金流、會員個人資料這類一旦出錯就影響很大的內容,搬家這件事就不該當成練手的場合。這種情況,交給有經驗的工程人員、或直接委外處理,會比自己摸索安全得多。

最後一條是底線中的底線,只要你還沒把一份可靠的備份放到主機以外的安全地方,就不要貿然執行任何一步。備份沒到位之前的每一個指令,都是沒有安全網的高空動作。

工具本身沒有問題,該掂量的是自己這個站扛得起多少風險,再決定要不要親自下場。

搬家外掛的進度條看起來友善,那條慢慢往前爬的線給人一種一切都在掌控中的錯覺。但真正決定一次搬家順不順利的,從來不是介面好不好看,而是你有沒有先弄懂資料庫裡到底存了什麼、換網址時哪裡最容易壞掉。願意打開終端機、看得懂 --dry-run 那張報表在說什麼的人,反而比盯著進度條乾等的人更早搬完,也更少在事後回頭補坑。