據(jù)到指定盤)
Linux CentOS Stream 9 一鍵卸載 MySQL 8.4.7 并重裝到指定盤干了這么多年 Linux 運維我?guī)缀趺總€月都能碰到這種場景服務(wù)器剛到手時圖省事MySQL 直接用默認方式裝上去數(shù)據(jù)一路往/var/lib/mysql里堆。等系統(tǒng)盤告警、df -h一敲發(fā)現(xiàn)/已經(jīng) 100% 的時候才想起來當初怎么沒把數(shù)據(jù)庫放在數(shù)據(jù)盤上。我這次處理的就是一臺 CentOS Stream 9 機器上面跑著 MySQL 8.4.7系統(tǒng)盤就剩 800M而另外掛載了一塊 1TB 的數(shù)據(jù)盤一直閑置。與其用軟鏈接把/var/lib/mysql挪過去不如直接卸載干凈、重裝到指定盤一步到位。這篇文章就把這套“卸載 重裝 數(shù)據(jù)遷移到指定盤”的流程完整記錄一下并且整理成腳本下次再遇到同類服務(wù)器可以直接抄作業(yè)。不管你是在學 Linux 安裝 MySQL 的新手還是天天跟數(shù)據(jù)庫目錄、磁盤布局打交道的運維老手這篇都值得看完。新手的收獲是知道 MySQL 到底怎么卸載才干凈、怎么指定數(shù)據(jù)目錄才不出錯老手的收獲是我把踩過的坑尤其是 SELinux 和 socket 路徑這兩個隱形殺手全部攤開來講清楚避免你重走彎路。1. 為什么要把 MySQL 重裝到指定盤方案選型與設(shè)計思路1.1 默認安裝路徑的問題根源MySQL 在 Linux 上用 RPM 包裝完之后數(shù)據(jù)目錄被固定在/var/lib/mysql日志寫到/var/log/mysqlsocket 文件放在/tmp/mysql.sock或者/var/lib/mysql/mysql.sock。這本身沒有毛病問題的關(guān)鍵在于/var屬于根分區(qū)。很多云主機根分區(qū)給的容量就 40G 到 50G系統(tǒng)本身吃掉一部分nginx 日志、業(yè)務(wù)備份再占掉一部分留給數(shù)據(jù)庫的余量非常有限。數(shù)據(jù)庫一漲起來根分區(qū)直接被打爆。我見過最典型的故障現(xiàn)場就是/var/lib/mysql下面的 binlog 和臨時文件把根分區(qū)寫滿MySQL 直接拒絕寫入業(yè)務(wù)側(cè)大量報錯。此時數(shù)據(jù)庫本身沒有壞純粹是磁盤空間耗盡。運維要做的無非兩條路——擴容根分區(qū)或者把數(shù)據(jù)目錄挪到獨立的數(shù)據(jù)盤上。擴容根分區(qū)要動云盤、擴分區(qū)、擴文件系統(tǒng)中間停機窗口很大而把 MySQL 的數(shù)據(jù)目錄搬到一塊干凈的數(shù)據(jù)盤不動系統(tǒng)盤、不擴容、不影響已經(jīng)掛載的其他服務(wù)明顯更劃算。1.2 “重裝到指定盤”而不是“軟鏈接挪目錄”把 MySQL 的數(shù)據(jù)目錄搬家業(yè)界常見的做法有三種。第一種是直接軟鏈接停庫把/var/lib/mysql整體拷貝到新盤然后mv /var/lib/mysql /var/lib/mysql.bak再ln -s /data/mysql /var/lib/mysql。這招在部分系統(tǒng)上確實能跑但隱患不小MySQL 升級時 RPM 包里的腳本有時會刪掉軟鏈接重建目錄SELinux 對軟鏈接上下文的識別也常有異常另外 systemd 的ProtectSystem等安全選項在某些版本下會阻止對軟鏈接目錄的正常訪問。第二種是 Mount Bind 掛載把數(shù)據(jù)盤掛載到/var/lib/mysql目錄看起來路徑不變底層其實是新盤。這個方法不用改配置文件適用性很廣但如果數(shù)據(jù)庫需要漂移、或者存在多個實例要分目錄部署時bind 掛載管理起來就比較繁瑣。第三種就是我采用的“真重裝”卸載舊 MySQL重新安裝時將數(shù)據(jù)目錄通過datadir配置項顯式指向數(shù)據(jù)盤比如/data/mysql。這是最干凈、最符合官方推薦的方式不存在任何路徑層面的兼容性問題。代價是要停機、要重裝、要重新初始化數(shù)據(jù)目錄。但配合自動化腳本整個流程可以在十分鐘內(nèi)完成風險完全可控。1.3 一鍵腳本的三個設(shè)計原則把整個流程腳本化最忌諱的就是寫一個“看起來能用其實一跑就崩”的腳本。我在設(shè)計這個一鍵腳本時給自己定了三條死規(guī)矩。第一備份永遠先于破壞。任何情況下腳本執(zhí)行到卸載步驟之前必須先做數(shù)據(jù)備份沒有備份直接拒絕繼續(xù)執(zhí)行。第二冪等性。腳本不管是第一次跑、中途失敗重新跑、還是對一臺已經(jīng)重裝過 MySQL 的機器跑都不應(yīng)該產(chǎn)生副作用。比如重復添加官方倉庫要能跳過目標數(shù)據(jù)目錄已存在時不能無腦覆蓋。第三分段可見。一鍵腳本不代表黑盒每個大步驟都要打印清晰的狀態(tài)提示并且要求每一步的執(zhí)行結(jié)果都被檢查失敗即中斷不能帶著錯誤往下走。基于這三條原則整體流程劃分為環(huán)境檢查、數(shù)據(jù)備份、卸載清理、倉庫配置、安裝、目錄初始化、配置寫入、啟動驗證八個階段。下面按實際執(zhí)行順序逐步展開。2. 動手之前的準備備份、磁盤評估與依賴確認2.1 盤點現(xiàn)有數(shù)據(jù)體量卸載之前必須先搞清楚一個問題這臺機器上的 MySQL 到底有多少數(shù)據(jù)刪了之后還能不能恢復。我處理的那臺機器上跑著好幾套應(yīng)用庫最大的庫有 47GB里面還有一堆統(tǒng)計表。直接在系統(tǒng)里敲du -sh /var/lib/mysql mysql -u root -p -e SELECT table_schema, ROUND(SUM(data_lengthindex_length)/1024/1024, 2) AS total_mb FROM information_schema.tables GROUP BY table_schema ORDER BY total_mb DESC;第一句看總目錄占用第二句從庫里按 schema 統(tǒng)計各庫容量。兩個都能跑的話數(shù)據(jù)體量基本就有數(shù)了。我遇到的現(xiàn)象是第二句因為庫太大、查詢慢差點以為自己連不上數(shù)據(jù)庫實際是 information_schema 統(tǒng)計時鎖表現(xiàn)象等了十幾秒才出結(jié)果不用慌。2.2 mysqldump 邏輯備份與物理備份雙保險我強烈建議邏輯備份和物理備份各做一份不要嫌麻煩。邏輯備份用mysqldump導出 SQL 文件方便重裝之后直接導入物理備份直接把整個/var/lib/mysql拷貝走防止 mysqldump 在導出過程中遇到個別損壞的表或權(quán)限問題導致漏數(shù)據(jù)。邏輯備份命令如下mkdir -p /backup/mysql_backup mysqldump -u root -p --all-databases --single-transaction --routines --triggers --events --set-gtid-purgedOFF /backup/mysql_backup/all_databases_$(date %F).sql這里說幾個容易被忽略的關(guān)鍵參數(shù)。--single-transaction配合 InnoDB 可以做一致性快照備份不鎖表在線執(zhí)行時不影響業(yè)務(wù)寫入--routines和--triggers必須加否則存儲過程、觸發(fā)器全部丟失--events備份事件調(diào)度器--set-gtid-purgedOFF是 MySQL 8.0/8.4 環(huán)境下從非復制實例導出時必須要加的不然導入時會把 GTID 歷史帶上可能引發(fā)主從復制沖突。物理備份更簡單也更快systemctl stop mysqld cp -rp /var/lib/mysql /backup/mysql_backup/var_lib_mysql_physical_$(date %F) systemctl start mysqld停庫再拷貝能保證數(shù)據(jù)文件處于一致狀態(tài)。如果業(yè)務(wù)允許長時間只讀先停庫備份完再啟動是最穩(wěn)的。如果不允許停機那就用rsync做兩次同步第一次在線同步第二次短暫鎖定寫入再同步增量。2.3 確認目標數(shù)據(jù)盤的掛載狀態(tài)備份做完接下來要看數(shù)據(jù)盤。目標盤的掛載點、文件系統(tǒng)類型、剩余空間都需要確認lsblk df -hT fdisk -l我這邊的情況是數(shù)據(jù)盤/dev/sdb已經(jīng)格式化成了 XFS掛載在/data可用空間 980GB。如果你的新盤還沒有掛載那就需要先分區(qū)、格式化、寫進/etc/fstab做持久掛載再繼續(xù)往下走。這里提醒一句目標掛載目錄最好是單獨的新目錄比如/data不要直接掛在/home或/root下面MySQL 會拒絕把數(shù)據(jù)目錄放到 home 目錄路徑下報錯信息是Datadir is inside home directory這個坑在手動初始化時非常常見。2.4 清理 CentOS Stream 9 的包管理依賴CentOS Stream 9 默認用 dnf 作為包管理器官方倉庫里就叫mysql-server。我機器上的 MySQL 8.4.7 是通過 MySQL 官方 Yum 倉庫裝的所以卸載前確認一下來源rpm -qa | grep -i mysql在有官方倉庫的機器上結(jié)果通常包含mysql-server、mysql84-libs、mysql84-common、mysql84-icu-data-files等。卸載時直接dnf remove mysql-server會把服務(wù)端去掉但留下的mysql84-libs等庫文件未必會一起移除需要手動再清理。如果你是通過dnf install mysql-server從 AppStream 裝的那包名可能略有差異但卸載思路一樣。還有一點非常重要卸載記錄一定要確認不能漏掉配置目錄。RPM 卸載時默認不會刪除/etc/my.cnf和/var/lib/mysql數(shù)據(jù)這是設(shè)計上防止誤刪的安全機制。但我們的訴求是“重裝到指定盤”必須主動清理這些殘留。3. 卸載腳本的執(zhí)行邏輯與清理細節(jié)3.1 卸載階段腳本實現(xiàn)我把卸載階段的核心代碼提出來這段可以直接單獨跑也可以合并進一鍵腳本#!/bin/bash # mysql_uninstall.sh # 用途在 CentOS Stream 9 上徹底卸載 MySQL 8.4.7 set -euo pipefail echo [1/4] 停止 MySQL 服務(wù) systemctl stop mysqld 2/dev/null || true systemctl disable mysqld 2/dev/null || true pkill -9 mysqld 2/dev/null || true echo [2/4] 確認備份完成 if [ ! -f /backup/mysql_backup/all_databases_*.sql ] [ ! -d /backup/mysql_backup/var_lib_mysql_physical_* ]; then echo 錯誤未檢測到任何備份文件拒絕卸載 exit 1 fi echo [3/4] 卸載 RPM 包 dnf remove -y mysql-server mysql84-server mysql84 2/dev/null || true dnf remove -y $(rpm -qa | grep -i mysql) 2/dev/null || true echo [4/4] 清理殘留文件 rm -rf /var/lib/mysql.old_$(date %s) 2/dev/null || true mv /var/lib/mysql /var/lib/mysql.old_$(date %s) rm -f /var/log/mysqld.log /var/log/mysql.log 2/dev/null || true rm -rf /etc/my.cnf /etc/my.cnf.d 2/dev/null || true echo 卸載完成。殘留文件已重命名為 /var/lib/mysql.old_*如需找回數(shù)據(jù)可以從此目錄恢復。這段腳本里有幾個小細節(jié)值得展開講講。停服務(wù)時我加了一個pkill -9 mysqld這是防止 mysqld 進程異常駐留。正常systemctl stop可以優(yōu)雅停機但一旦遇到卡死的連接或者磁盤 IO 阻塞stop 會一直卡住這時候強殺是無奈但有效的辦法??紤]到腳本的自動化屬性這一行保留但日常手動操作時還是建議先systemctl stop多等幾秒不要一上來就 pkill。備份檢查這步我用的是通配符判斷文件是否存在。你可能覺得set -euo pipefail下如果沒有任何備份文件會直接退出不需要額外判斷。但實際上 bash 的set -e對if判斷內(nèi)部命令是豁免的所以需要顯式寫這個檢查。備份檢查這段絕非形式主義我吃了太多教訓卸載腳本里如果沒有這層防線一臺忘記備份的機器跑了卸載命令后果就是徹底涼涼。dnf remove -y $(rpm -qa | grep -i mysql)這行的威力很大會把所有含 mysql 字樣的包全部移除。如果不加過濾條件有可能把mysql-connector-odbc等客戶端相關(guān)包也一并刪掉所以在實際使用時建議把這一行保留但提前用rpm -qa | grep -i mysql確認一下列表內(nèi)容。3.2 為什么用“重命名”而不是“直接刪除”數(shù)據(jù)目錄腳本里把/var/lib/mysql重命名成帶時間戳的舊目錄而不是直接rm -rf。這個設(shè)計是因為數(shù)據(jù)目錄里可能有你還沒意識到的價值。比如 binlog 中可能有某些時間點的增量數(shù)據(jù)或者某個庫的某些表是 MyISAM 引擎mysqldump 不一定能完整導出。保留舊目錄等于多一個后悔藥而且重命名操作比刪除快得多、安全得多。等重裝完成、數(shù)據(jù)驗證通過之后再手動清理這個舊目錄也不遲。清理/etc/my.cnf和/etc/my.cnf.d也是必須的因為重裝之后如果舊配置里還有指向舊 socket 或舊 datadir 的路徑新實例很容易起不來。特別是 CentOS Stream 9 上我遇到過/etc/my.cnf.d/mysql-server.cnf這類分段配置不刪干凈的話改了主配置卻總被下面的分段配置覆蓋排查半天才知道原因。3.3 卸載結(jié)果的驗證方法腳本跑完不要直接進入安裝階段。先驗證卸載是否真的干凈rpm -qa | grep -i mysql # 應(yīng)無任何輸出 which mysql # 應(yīng)提示命令不存在 ls /var/lib/mysql # 目錄已不存在或只剩重命名后的目錄這一步如果發(fā)現(xiàn)rpm -qa還有殘留需要重新執(zhí)行dnf remove。如果which mysql還能找到說明系統(tǒng)的 PATH 里還有殘留的二進制可能是手動編譯安裝留下的這類分布在路徑層面不容易清干凈但不影響重裝安裝官方 RPM 時會把新版二進制覆蓋到/usr/bin/mysql。4. 重裝 MySQL 8.4.7 并指向數(shù)據(jù)盤關(guān)鍵配置與初始化4.1 安裝 MySQL 官方倉庫CentOS Stream 9 的 AppStream 倉庫自帶的 MySQL 版本通常是 8.0 系列而標題里指定要 MySQL 8.4.7這個是 8.4 LTS 系列必須用 MySQL 官方 Yum 倉庫來裝。rpm -ivh https://dev.mysql.com/get/mysql84-community-release-el9-1.noarch.rpm執(zhí)行完可以用下面的命令確認倉庫是否生效dnf repolist | grep mysql正常能看到mysql84-community和mysql84-community-source兩個倉庫。接下來安裝服務(wù)端dnf install -y mysql-server等待安裝完成。安裝過程會把mysqld服務(wù)放到/usr/sbin/mysqld同時生成默認的/etc/my.cnf。這里有個細節(jié)MySQL 8.4 默認的/etc/my.cnf是空配置絕大多數(shù)配置項都依賴默認值所以后續(xù)我們要往里面寫入自定義的 datadir 和 socket 路徑。4.2 創(chuàng)建目標數(shù)據(jù)目錄并處理權(quán)限數(shù)據(jù)盤掛載在/data那就先建目錄、改權(quán)限mkdir -p /data/mysql chown -R mysql:mysql /data/mysql chmod 750 /data/mysqlchown改成mysql:mysql這一點不用多說MySQL 的 systemd 服務(wù)默認以 mysql 用戶運行。但chmod 750可能會被一些人忽略。如果目錄權(quán)限是 755其他用戶也能進入目錄讀元數(shù)據(jù)文件這不符合最小權(quán)限原則如果設(shè)成 700mysql 用戶能訪問但 MySQL 的目錄內(nèi)還會創(chuàng)建臨時文件750 是折中且穩(wěn)妥的方案。4.3 修改 my.cnf 核心配置在/etc/my.cnf里寫入[mysqld] ###### 基礎(chǔ)配置 ###### datadir/data/mysql socket/data/mysql/mysql.sock pid-file/data/mysql/mysqld.pid log-error/var/log/mysqld.log ###### 字符集與時區(qū) ###### character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone8:00 ###### 連接優(yōu)化可選 ###### max_connections1000 skip-name-resolve1 innodb_buffer_pool_size2G [client] socket/data/mysql/mysql.sock這里最關(guān)鍵的三個變量是datadir、socket、pid-file。只要 datadir 變了socket 和 pid-file 建議也一起變否則會出現(xiàn)一個很隱蔽的故障mysqld 把 socket 文件放在了/data/mysql/mysql.sock但客戶端默認去/var/lib/mysql/mysql.sock找結(jié)果報ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/lib/mysql/mysql.sock。網(wǎng)絡(luò)熱詞里正好有這條報錯見過的人絕對不少。所以[client]段里的 socket 也要同步指向新位置這樣本地連接才能正常走通。skip-name-resolve1是我習慣加上的配置它讓 MySQL 不再對客戶端 IP 做反向 DNS 解析能減少連接延遲和 DNS 故障導致的連接問題。副作用是user表中的 host 字段必須用 IP 或用localhost不能用域名大多數(shù)場景都沒問題。default-time-zone8:00這塊要留意一下如果你用的是 UTC 時區(qū)服務(wù)器不顯式指定可能會導致應(yīng)用側(cè)時間差 8 小時。判斷系統(tǒng)時區(qū)用timedatectl和 SQL 里的SELECT NOW();對照一下即可。4.4 SELinux 策略最容易踩的大坑CentOS Stream 9 默認開啟 SELinux而且處于 enforcing 模式。MySQL RPM 包的 SELinux 策略默認放行了/var/lib/mysql目錄但一旦我們把 datadir 指到/data/mysqlmysqld 在啟動時訪問這個新目錄就會觸發(fā) SELinux denial。日志里通常出現(xiàn)類似Jan 10 12:00:01 host mysqld[1234]: Cant open the mysql.plugin table.或者Jan 10 12:00:01 host kernel: audit: type1400 audit(...): avc: denied { write } for pid1234 commmysqld namemysql devsdb1 scontextsystem_u:system_r:mysqld_t:s0 tcontextsystem_u:object_r:unlabeled_t:s0 tclassdir我自己一開始沒注意啟動失敗跑去看/var/log/mysqld.log里面啥都沒有但是journalctl -u mysqld能看到 SELinux 的審計日志。這個問題有兩種解決辦法。第一種最正規(guī)給新目錄配置 mysqld 類型的 SELinux 文件上下文然后 restorecon。semanage fcontext -a -t mysqld_db_t /data/mysql(/.*)? restorecon -Rv /data/mysql如果系統(tǒng)沒有semanage命令先裝工具包dnf install -y policycoreutils-python-utils第二種是臨時測試時用chcon -R -t mysqld_db_t /data/mysqlchcon直接修改目錄的 SELinux 標簽但不會持久化文件系統(tǒng)重新標記后可能會被還原。日常運維建議老老實實用semanage fcontext加restorecon一次配置永久生效。如果你覺得 SELinux 太麻煩想直接關(guān)掉那就是另一條路。在/etc/selinux/config里把SELINUXenforcing改成permissive重啟或執(zhí)行setenforce 0。但我不建議你為 MySQL 單獨關(guān)閉 SELinux因為生產(chǎn)環(huán)境開 SELinux 是基本的安全底線正確配置策略并沒有想象中復雜花幾分鐘改上下文就能解決沒必要降低整個系統(tǒng)的安全等級。4.5 初始化數(shù)據(jù)目錄MySQL 8.4 安裝完成之后不會像舊版本那樣自動幫你初始化數(shù)據(jù)目錄需要手動執(zhí)行mysqld --initialize。有兩種初始化模式。第一種自動生成臨時隨機密碼mysqld --initialize --usermysql初始化完成后臨時密碼打印在錯誤日志里grep temporary password /var/log/mysqld.log拿到密碼后要盡快登錄并修改。第二種生成空密碼的 root 賬號mysqld --initialize-insecure --usermysql這適合自動化腳本場景不需要解析日志就能直接登錄然后立即用 SQL 設(shè)置新密碼。我在一鍵腳本里用的是--initialize-insecure因為自動化處理隨機密碼特別痛苦。這里必須強調(diào)初始化命令必須在/etc/my.cnf配置完成之后執(zhí)行或者配合--datadir/data/mysql參數(shù)顯式指定。如果初始化時 my.cnf 還沒改mysqld 會在默認的/var/lib/mysql初始化你的 datadir 配置就白寫了。我遇到過頭疼的情況配置寫好了但忘了清空/data/mysql下的殘留文件執(zhí)行初始化時報錯[ERROR] InnoDB: The innodb_system data file ibdata1 must be writable原因就是舊文件權(quán)限不對或殘留沖突。重新清空目錄后跑就正常了。5. 啟動服務(wù)、驗證數(shù)據(jù)目錄與恢復備份數(shù)據(jù)5.1 啟動服務(wù)并設(shè)置開機自啟配置和初始化都完成之后執(zhí)行systemctl daemon-reload systemctl enable mysqld --now systemctl status mysqldsystemctl status輸出里有Active: active (running)就說明啟動成功。此時馬上驗證 datadir 是否生效mysql -uroot -p -e SHOW VARIABLES LIKE datadir; mysql -uroot -p -e SHOW VARIABLES LIKE socket;輸出應(yīng)該指向/data/mysql/和/data/mysql/mysql.sock。如果你用--initialize-insecure初始化此時 mysql 的 root 密碼是空的立刻修改mysql -uroot -p --connect-expired-password -e ALTER USER rootlocalhost IDENTIFIED BY YourNewStrongPass123!;MySQL 8.4 默認的密碼策略要求長度、大小寫、數(shù)字、特殊字符太簡單的密碼會直接被拒絕。5.2 從備份導入數(shù)據(jù)重裝后的 MySQL 是一張白紙業(yè)務(wù)庫全部要重新導入。用邏輯備份恢復mysql -uroot -p /backup/mysql_backup/all_databases_20250110.sql如果備份文件比較大導入時可以把進度打到日志里mysql -uroot -p --force /backup/mysql_backup/all_databases_20250110.sql 2 /backup/mysql_backup/import_error.log導入完成之后務(wù)必做一次對比校驗mysql -uroot -p -e SELECT table_schema, COUNT(*) AS table_count FROM information_schema.tables GROUP BY table_schema;再抽查幾張業(yè)務(wù)大表的行數(shù)是否與原備份體現(xiàn)的數(shù)量一致。老話講“沒有驗證的恢復等于沒恢復”這一步不能省。5.3 一鍵整合腳本總覽我把整個流程整合成了一個完整腳本放在/root/mysql_relocate.sh。結(jié)構(gòu)和前面的分步講解完全對應(yīng)但有幾點整合時需要注意檢測是否重復執(zhí)行、備份檢查邏輯復用、日志輸出到固定文件。核心框架如下#!/bin/bash set -euo pipefail DATA_DISK_MOUNT/data DATA_DIR/data/mysql BACKUP_DIR/backup/mysql_backup MYSQL_ROOT_PASSYourNewStrongPass123! log() { echo [$(date %Y-%m-%d %H:%M:%S)] $*; } fail() { echo [$(date %Y-%m-%d %H:%M:%S)] ERROR: $*; exit 1; } # 1. 檢查是否 root 執(zhí)行 [ $(id -u) -eq 0 ] || fail 請使用 root 執(zhí)行 # 2. 檢查目標盤的掛載情況 [ -d $DATA_DISK_MOUNT ] || fail 數(shù)據(jù)盤掛載目錄不存在 $DATA_DISK_MOUNT # 3. 備份 log 開始備份所有數(shù)據(jù)庫... systemctl stop mysqld 2/dev/null || true mkdir -p $BACKUP_DIR cp -rp /var/lib/mysql $BACKUP_DIR/var_lib_mysql_physical_$(date %F) || true systemctl start mysqld 2/dev/null || true mysqldump -u root -p$MYSQL_ROOT_PASS --all-databases --single-transaction --routines --triggers --events --set-gtid-purgedOFF $BACKUP_DIR/all_databases_$(date %F).sql || fail 備份失敗 # 4. 卸載 log 卸載舊版 MySQL... systemctl stop mysqld 2/dev/null || true systemctl disable mysqld 2/dev/null || true dnf remove -y mysql-server mysql84-server mysql84 2/dev/null || true dnf remove -y $(rpm -qa | grep -i mysql) 2/dev/null || true [ -d /var/lib/mysql ] mv /var/lib/mysql /var/lib/mysql.old_$(date %s) # 5. 安裝 log 安裝 MySQL 8.4.7... rpm -ivh https://dev.mysql.com/get/mysql84-community-release-el9-1.noarch.rpm 2/dev/null || true dnf install -y mysql-server # 6. 寫入配置 log 寫入 my.cnf 配置... cat /etc/my.cnf EOF [mysqld] datadir$DATA_DIR socket$DATA_DIR/mysql.sock pid-file$DATA_DIR/mysqld.pid log-error/var/log/mysqld.log character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone8:00 max_connections1000 skip-name-resolve1 innodb_buffer_pool_size2G [client] socket$DATA_DIR/mysql.sock EOF # 7. 創(chuàng)建數(shù)據(jù)目錄 SELinux 上下文 log 準備數(shù)據(jù)目錄... mkdir -p $DATA_DIR chown -R mysql:mysql $DATA_DIR chmod 750 $DATA_DIR semanage fcontext -a -t mysqld_db_t $DATA_DIR(/.*)? 2/dev/null || true restorecon -Rv $DATA_DIR # 8. 初始化并啟動 log 初始化數(shù)據(jù)目錄... rm -rf $DATA_DIR/* mysqld --initialize-insecure --usermysql systemctl enable mysqld --now # 9. 修改 root 密碼 log 設(shè)置 root 密碼... mysql -uroot --connect-expired-password -e ALTER USER rootlocalhost IDENTIFIED BY $MYSQL_ROOT_PASS; # 10. 導入備份 log 導入備份數(shù)據(jù)... mysql -uroot -p$MYSQL_ROOT_PASS --force $BACKUP_DIR/all_databases_*.sql log 全部完成。建議執(zhí)行 mysql -uroot -p 登錄驗證。這段腳本里有兩個地方是刻意設(shè)計的。一個是第 3 步先做物理備份再做邏輯備份順序不能反。如果機器上 mysqld 因為磁盤滿起不來邏輯備份導出很可能失敗但物理備份只要磁盤能讀就能成功復制。另一個是第 8 步rm -rf $DATA_DIR/*這一步保證mysqld --initialize-insecure不會因舊文件沖突失敗。配合前面“備份無論如何都要先完成”的防線這里的刪除是安全的。6. 常見問題與排查技巧實錄6.1 錯誤速查表以下這些錯誤在我處理過程中基本都會遇到按頻率從高到低整理成一張表方便你對照排查。錯誤現(xiàn)象根本原因解決方法Cant connect to local MySQL server through socket /var/lib/mysql/mysql.socksocket 路徑不匹配服務(wù)端與客戶端配置不一致確認/etc/my.cnf的[mysqld]和[client]妥協(xié)同一個 socket 路徑啟動失敗日志出現(xiàn)avc: denied { write } ... mysqld_t ... unlabeled_tSELinux 未放行新 datadirsemanage fcontext -a -t mysqld_db_t /data/mysql(/.*)?; restorecon -Rv /data/mysqlmysqld: Cant create/write to file /data/mysql/... (Errcode: 13 - Permission denied)目錄權(quán)限不對或 mysql 用戶無寫權(quán)限chown -R mysql:mysql /data/mysql并確認父目錄/data可被 mysql 用戶穿越[ERROR] InnoDB: The innodb_system data file ibdata1 must be writable/data/mysql有殘留舊文件或權(quán)限不正確清空數(shù)據(jù)目錄內(nèi)容后重新--initialize-insecureERROR 1045 (28000): Access denied for user rootlocalhost密碼錯誤或者用了臨時密碼沒修改初始化日志找臨時密碼或--initialize-insecure后用空密碼登錄再改密遠程連接 MySQL 報Authentication plugin caching_sha2_password cannot be loaded客戶端版本過舊不支持 MySQL 8.4 默認認證插件升級客戶端驅(qū)動到新版本或在服務(wù)端為用戶設(shè)置mysql_native_password6.2 四個值得寫下來的排查心得第一個心得任何奇怪的啟動失敗先去journalctl -u mysqld看日志不要只看/var/log/mysqld.log。mysqld 的 error log 在配置階段可能還沒生效而 systemd journal 記錄的是啟動瞬間的完整輸出包括 SELinux 審計信息。我以前經(jīng)常一頭扎進/var/log/mysqld.log里面只有半句話后來才發(fā)現(xiàn)關(guān)鍵信息全在 journal 里。第二個心得skip-name-resolve1加上之后如果應(yīng)用原來用主機名連 MySQL會直接連不上因為 MySQL 不再做反向解析。報錯是Host 192.168.1.10 is not allowed to connect to this MySQL server。解決方式是授權(quán)時直接用 IP 地址寫 host。如果業(yè)務(wù)里有大量域名連接不建議開這個參數(shù)。第三個心得binlog 占空間的問題很容易被忽視。舊實例可能在/var/lib/mysql里堆積了很多 binlog 文件每個 1GB幾十個下來就是幾十 GB。重裝之后如果不打算做基于 binlog 的主從復制可以在 my.cnf 里加expire_logs_days7MySQL 8.4 用binlog_expire_logs_seconds避免新實例沒跑幾天又把數(shù)據(jù)盤占滿。第四個心得如果你要直接拿舊數(shù)據(jù)目錄恢復就是物理備份那種方式千萬注意 MySQL 8.4 的auto.cnf文件。這個文件記錄了 server UUID直接復制舊數(shù)據(jù)目錄到新機器后如果新機器原來的auto.cnf和舊的不一樣需要刪除/data/mysql/auto.cnf再啟動否則主從場景下 UUID 沖突會導致復制建立失敗。單機運行可能不報錯但為了規(guī)范恢復物理備份時最好刪掉自動生成的auto.cnf。6.3 數(shù)據(jù)驗證時的關(guān)鍵一步安裝、導入完成后還有一步容易被忽略驗證舊目錄中的庫是否都已經(jīng)出現(xiàn)在新實例中。我的做法是寫了一個快速比對腳本mysql -uroot -p -N -e SHOW DATABASES; | grep -v -E ^(information_schema|performance_schema|mysql|sys)$ | sort /tmp/db_new.list然后再從備份目錄的物理備份中直接讀目錄名稱find /backup/mysql_backup/var_lib_mysql_physical_* -maxdepth 1 -type d | awk -F/ {print $NF} | grep -v -E ^\.|sys|undo|binlog|tmp | sort /tmp/db_old.list diff /tmp/db_new.list /tmp/db_old.list如果 diff 有輸出說明有庫沒有恢復進來需要針對性處理。這個方法不復雜但能救命。有一次我導入時因為 SQL 文件里存在已刪除表的誤操作某個庫竟然沒被導進來如果沒有這步比對業(yè)務(wù)上線半天后才會暴露問題。7. 最后再說一點實際體會這套流程我在生產(chǎn)環(huán)境上完整跑過不止一次每次都能順利收尾但每次也都會因為環(huán)境差異多一些新的發(fā)現(xiàn)。比如有的機器掛載點是/mnt/data有的數(shù)據(jù)盤格式是 ext4有的系統(tǒng)里還殘留著低版本的 MySQL 客戶端這些都會讓腳本的適配層不斷變厚。我個人最大的體會是卸載和重裝本身不難難的是做到“不丟數(shù)據(jù)、不破壞環(huán)境、出了問題能回滾”。所以就算你不需要一鍵腳本也請務(wù)必保留舊的/var/lib/mysql重命名目錄至少保留一個月再清理。磁盤便宜數(shù)據(jù)無價在這個事情上多留一手永遠不虧。另外想提醒的是腳本里的 root 密碼、緩沖池大小、時區(qū)這些參數(shù)換成你自己的環(huán)境時一定不要照抄。密碼策略、內(nèi)存大小、業(yè)務(wù)時區(qū)都是高度個性化的直接套用默認值最容易在后續(xù)維護中踩坑。把這套腳本當成一個骨架根據(jù)實際場景調(diào)整血肉才是正確的用法。