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