據(jù)目錄修改與遷移實(shí)戰(zhàn):從datadir配置到避坑指南)
1. 為什么MySQL數(shù)據(jù)目錄成了很多人繞不過去的坑先說一個真實(shí)的場景很多人在用MySQL的時候一開始都是在本地測試環(huán)境裝好就完事數(shù)據(jù)默認(rèn)丟在安裝目錄下的data文件夾里。等到項目上線、數(shù)據(jù)文件開始漲到幾十個G甚至上百G的時候才發(fā)現(xiàn)系統(tǒng)盤空間不夠了這時候才急著想把數(shù)據(jù)目錄挪到數(shù)據(jù)盤上。如果之前沒有仔細(xì)規(guī)劃過這個遷移過程往往伴隨著停機(jī)、權(quán)限報錯、SELinux攔截、服務(wù)起不來等各種問題稍微處理不好寶貴的數(shù)據(jù)就可能面臨風(fēng)險。這個主題適合所有在用MySQL的人不管你是剛裝好MySQL想規(guī)劃目錄結(jié)構(gòu)的新手還是已經(jīng)在生產(chǎn)環(huán)境里碰到磁盤告警、準(zhǔn)備做數(shù)據(jù)目錄遷移的運(yùn)維同學(xué)這篇文章的內(nèi)容都值得你花幾分鐘看懂。我會從MySQL數(shù)據(jù)目錄的基本概念出發(fā)講清楚為什么要改目錄、怎么查看當(dāng)前數(shù)據(jù)目錄、三種常用的修改方式以及我在實(shí)際環(huán)境中踩過的那些坑和總結(jié)出的排查思路。MySQL作為目前最流行的開源關(guān)系型數(shù)據(jù)庫之一它的數(shù)據(jù)存儲機(jī)制其實(shí)并不復(fù)雜所有數(shù)據(jù)庫、表、索引、日志等文件最終都以文件的形式落在操作系統(tǒng)的一個固定目錄下這個目錄在MySQL里叫datadir。默認(rèn)情況下Linux發(fā)行版通過包管理器安裝的MySQL或者M(jìn)ariaDB數(shù)據(jù)目錄一般在/var/lib/mysql而Windows下用安裝包裝的MySQL默認(rèn)數(shù)據(jù)目錄通常在C:\ProgramData\MySQL\MySQL Server 8.0\Data。生產(chǎn)環(huán)境里系統(tǒng)盤和數(shù)據(jù)盤分開是標(biāo)配數(shù)據(jù)目錄放系統(tǒng)盤既不安全也容易爆盤所以學(xué)會修改數(shù)據(jù)目錄幾乎是每個MySQL使用者都繞不開的基本功。2. 核心概念MySQL數(shù)據(jù)目錄里到底放了什么2.1 datadir參數(shù)的含義與作用范圍datadir是MySQL服務(wù)器的核心參數(shù)之一它定義了所有數(shù)據(jù)庫文件存放的根路徑。你可以用下面這條SQL直接查看當(dāng)前的設(shè)置SHOW VARIABLES LIKE datadir;我以前在排查問題的時候發(fā)現(xiàn)不少同事其實(shí)對datadir的理解比較模糊他們以為數(shù)據(jù)目錄就是某個數(shù)據(jù)庫的文件夾其實(shí)不是。datadir是整個MySQL實(shí)例存放所有數(shù)據(jù)的根目錄在這個根目錄下你會看到每個數(shù)據(jù)庫對應(yīng)一個子目錄比如mysql、sys、你自己創(chuàng)建的庫除此之外還有一些全局文件包括ibdata1系統(tǒng)表空間存放數(shù)據(jù)字典、undo log等、ib_logfile0和ib_logfile1redo log8.0.30版本之后默認(rèn)使用#innodb_redo目錄、undo_001和undo_002undo log文件、binlog.000001等二進(jìn)制日志文件如果開啟了binlog以及auto.cnf服務(wù)器UUID和mysql.ibd數(shù)據(jù)字典表空間等。理解了這個結(jié)構(gòu)你會發(fā)現(xiàn)一個很關(guān)鍵的點(diǎn)datadir不只是某一個庫的數(shù)據(jù)它是整個MySQL實(shí)例的“家”。所以修改數(shù)據(jù)目錄本質(zhì)上不是“把庫目錄搬走”而是“給整個實(shí)例搬家”這就決定了遷移過程必須非常謹(jǐn)慎不能只拷貝業(yè)務(wù)庫目錄就完事。2.2 默認(rèn)目錄與初始化流程的關(guān)聯(lián)MySQL在初始化實(shí)例的時候會把系統(tǒng)數(shù)據(jù)庫mysql、performance_schema、sys等和各種基礎(chǔ)表創(chuàng)建到datadir指定的目錄里。如果你用的是發(fā)行版自帶的包管理器安裝比如CentOS的yum、Ubuntu的apt安裝完成后服務(wù)啟動時會自動執(zhí)行初始化腳本默認(rèn)路徑就是/var/lib/mysql。這里有個比較容易出問題的地方有些人在安裝MySQL 8.0后直接修改my.cnf里的datadir路徑然后重啟服務(wù)結(jié)果服務(wù)起不來報錯信息是找不到mysql.ibd或者ibdata1。原因很簡單——你改了配置文件但新路徑下根本沒有初始化的數(shù)據(jù)文件。MySQL的啟動邏輯是先讀配置拿到datadir然后到這個目錄下去找系統(tǒng)表空間和數(shù)據(jù)字典找不到就直接啟動失敗而不是自動幫你初始化。所以修改數(shù)據(jù)目錄有兩種完全不同的場景一種是在安裝完成后、初始化前就指定好目錄另一種是已經(jīng)跑了一段時間需要把現(xiàn)有數(shù)據(jù)完整遷移過去。這兩種場景的操作步驟不一樣后面我會分別講清楚。2.3 8.0版本相比5.7有哪些需要特別注意的差異如果你是從MySQL 5.7遷到8.0或者直接在8.0上做目錄修改有幾個差異點(diǎn)必須心里有數(shù)第一MySQL 8.0的數(shù)據(jù)字典是集中管理的存儲在mysql.ibd中不再像5.7那樣每個表都有一個.frm文件。這意味著8.0對數(shù)據(jù)目錄的完整性要求更高如果拷貝數(shù)據(jù)時漏了文件啟動時報錯會更早、更隱蔽。第二8.0.30版本開始redo log默認(rèn)存放在#innodb_redo這個子目錄里而不再是以ib_logfile0、ib_logfile1這種平鋪文件的形式存在。遷移數(shù)據(jù)時這個目錄也要一并拷貝。第三8.0在初始化方式和密碼策略上也有變化mysql_install_db腳本在官方壓縮包中不再提供統(tǒng)一使用mysqld --initialize來初始化。這直接影響你在修改數(shù)據(jù)目錄后如何生成初始數(shù)據(jù)文件。這些差異如果你不了解很容易在網(wǎng)上搜到5.7時代的教程照著操作卻發(fā)現(xiàn)行不通白白浪費(fèi)時間。3. 動手之前梳理三種修改數(shù)據(jù)目錄的常見思路3.1 直接修改my.cnf配置文件這是最直觀的思路編輯MySQL的配置文件Linux下一般是/etc/my.cnf或者/etc/mysql/my.cnf在[mysqld]段下修改datadir參數(shù)然后重啟服務(wù)。[mysqld] datadir/data/mysql但這里有一個隱含前提/data/mysql這個目錄必須已經(jīng)存在并且里面必須有完整的、可用的數(shù)據(jù)文件。如果目錄是空的MySQL是啟動不起來的。所以“直接修改配置”這句話通常只是整個流程的最后一步前面還得做初始化或者數(shù)據(jù)拷貝。這種方式的優(yōu)點(diǎn)是簡單直接、配置路徑清晰適合新裝環(huán)境缺點(diǎn)是一旦數(shù)據(jù)目錄已經(jīng)存有數(shù)據(jù)光改配置是不夠的還需要做遷移。3.2 通過遷移方式把原數(shù)據(jù)整體搬走遷移方式適用于已經(jīng)運(yùn)行了一段時間的MySQL實(shí)例操作核心是先停服務(wù)把原datadir下的所有文件原樣拷貝到新目錄再修改配置指向新目錄最后啟動服務(wù)驗(yàn)證。這種方式的優(yōu)點(diǎn)是你的數(shù)據(jù)文件、權(quán)限、目錄結(jié)構(gòu)都是現(xiàn)成的只要拷貝完整基本不會出問題缺點(diǎn)是拷貝大文件耗時較長期間服務(wù)需要停機(jī)。很多人會問能不能不拷貝直接用ln -s軟鏈接把新目錄鏈接到原路徑我在實(shí)際中也試過確實(shí)可以但MySQL官方并不推薦在生產(chǎn)環(huán)境這么做因?yàn)檐涙溄訒屇夸浗Y(jié)構(gòu)變得不透明后續(xù)做備份、升級、排查問題時很容易繞暈。除非你的環(huán)境非常特殊否則還是老老實(shí)實(shí)做數(shù)據(jù)遷移吧。3.3 通過初始化新環(huán)境再導(dǎo)入數(shù)據(jù)這種方式等于重建一個全新的MySQL實(shí)例把datadir指定到新目錄初始化完成后再把舊庫的數(shù)據(jù)通過邏輯備份mysqldump或物理備份的方式導(dǎo)入到新庫。適合跨大版本遷移比如5.7升8.0、操作系統(tǒng)更換、或者你想順便做一次數(shù)據(jù)清理的場景。優(yōu)點(diǎn)是不用關(guān)心底層文件格式是否兼容8.0和5.7的物理文件結(jié)構(gòu)差異很大不能直接拷貝缺點(diǎn)是需要停機(jī)、導(dǎo)入時間可能很長、而且要人工介入的地方多操作風(fēng)險也不小。一句話總結(jié)小數(shù)據(jù)量、正常停機(jī)窗口內(nèi)能完成拷貝的選方案2跨版本遷移或者數(shù)據(jù)量特別大、拷貝時間遠(yuǎn)大于導(dǎo)入時間的選方案3全新環(huán)境安裝直接改配置加初始化即可。4. 實(shí)操全流程以Linux環(huán)境MySQL 8.0為例完整走一遍4.1 第一步確認(rèn)當(dāng)前狀態(tài)和數(shù)據(jù)量動手之前先把現(xiàn)狀查清楚。我一般依次執(zhí)行這幾條命令# 查看MySQL當(dāng)前的數(shù)據(jù)目錄 mysql -uroot -p -e SHOW VARIABLES LIKE datadir; # 查看各數(shù)據(jù)庫占用空間便于規(guī)劃新目錄容量 du -sh /var/lib/mysql/* # 查看磁盤分區(qū)情況 df -h這一步的目標(biāo)很明確知道原數(shù)據(jù)有多大、新目標(biāo)分區(qū)有沒有足夠的空間、當(dāng)前配置文件在哪個路徑、服務(wù)是怎么啟動的systemd還是init腳本。很多時候遷移失敗都是因?yàn)榍捌跊]查清楚就直接操作結(jié)果做到一半發(fā)現(xiàn)目標(biāo)盤空間不夠進(jìn)退兩難。接著還要確認(rèn)配置文件的位置和生效順序。MySQL讀取配置文件的順序非常容易忽略它依次讀取/etc/my.cnf、/etc/mysql/my.cnf、~/.my.cnf等后面的配置會覆蓋前面的不同發(fā)行版還不太一樣。我習(xí)慣用下面這個命令確認(rèn)最終生效的配置從哪里來的mysqld --verbose --help | grep -A 1 Default options這個命令會直接告訴你當(dāng)前mysqld會讀取哪些配置文件順序是什么非常直觀。4.2 第二步規(guī)劃新目錄與權(quán)限設(shè)置假設(shè)我們要把數(shù)據(jù)目錄遷到/data/mysql首先要創(chuàng)建目錄、歸屬給mysql用戶mkdir -p /data/mysql chown -R mysql:mysql /data/mysql chmod 750 /data/mysql這里有兩個容易踩坑的點(diǎn)。第一目錄權(quán)限。MySQL服務(wù)通常以mysql系統(tǒng)用戶運(yùn)行新目錄必須讓這個用戶有完全讀寫權(quán)限否則啟動時會報Permission denied。我建議權(quán)限直接設(shè)750所有者和組都設(shè)為mysql這樣既安全又不會出問題。有的教程會讓你chmod 777生產(chǎn)環(huán)境千萬別這么干數(shù)據(jù)目錄對任何其他用戶開放是完全不可接受的。第二SELinux。很多CentOS環(huán)境默認(rèn)開著SELinux就算你目錄權(quán)限完全正確MySQL也可能因?yàn)镾ELinux策略攔截而無法讀取新目錄。檢查SELinux是否開啟用命令getenforce如果是Enforcing狀態(tài)要么對數(shù)據(jù)目錄設(shè)置正確的上下文標(biāo)簽要么臨時關(guān)閉SELinux來定位問題。給目錄打標(biāo)簽的命令是semanage fcontext -a -t mysqld_db_t /data/mysql(/.*)? restorecon -Rv /data/mysql如果你不確定這個操作是否必須可以先用ls -Z /var/lib/mysql看下原目錄的SELinux標(biāo)簽然后保持一致即可。4.3 第三步停服、拷貝數(shù)據(jù)、改配置確認(rèn)新目錄準(zhǔn)備就緒后開始正式遷移。操作順序不能亂停掉MySQL服務(wù)systemctl stop mysqld確認(rèn)服務(wù)已經(jīng)停止沒有殘留進(jìn)程ps -ef | grep mysqld用rsync拷貝數(shù)據(jù)文件這里我強(qiáng)烈建議用rsync而不是cp。原因有兩點(diǎn)一是rsync支持?jǐn)帱c(diǎn)續(xù)傳數(shù)據(jù)量大的時候萬一中斷不用從頭再來二是rsync可以很好地保留文件權(quán)限、時間戳、屬主信息。命令如下rsync -av /var/lib/mysql/ /data/mysql/注意源目錄末尾的斜杠不能省略它表示拷貝目錄內(nèi)的所有內(nèi)容到目標(biāo)目錄而不是把mysql這個文件夾本身嵌套進(jìn)去。修改配置文件[mysqld] datadir/data/mysql socket/data/mysql/mysql.sock pid-file/data/mysql/mysqld.pid這里我需要特別提一下socket和pid-file。默認(rèn)情況下socket文件和pid文件也會生成在數(shù)據(jù)目錄里如果你只改了datadir重啟后socke文件路徑變了很多依賴socket連接的程序比如本地命令行連接會報錯。所以這三個參數(shù)最好一起改保持一致。當(dāng)然如果你的socket是通過/tmp/mysql.sock或者/var/run/mysqld/mysqld.sock訪問的并且你不想影響現(xiàn)有應(yīng)用的連接那也可以不動socket路徑只改datadir。這個取舍根據(jù)你實(shí)際環(huán)境來定。啟動服務(wù)并驗(yàn)證systemctl start mysqld systemctl status mysqld mysql -uroot -p -e SHOW VARIABLES LIKE datadir;如果啟動成功并且查到的datadir已經(jīng)變成/data/mysql恭喜你遷移最核心的步驟就完成了。4.4 新裝環(huán)境如何在初始化階段直接指定數(shù)據(jù)目錄如果你是在新機(jī)器上裝MySQL 8.0其實(shí)在一開始就可以把數(shù)據(jù)目錄設(shè)置好這樣后面就不用折騰遷移了。兩種方式方式一在初始化命令里指定參數(shù)。官方tar包安裝的MySQL 8.0初始化命令是mysqld --initialize --datadir/data/mysql --usermysql方式二先把配置文件寫好再初始化。編輯/etc/my.cnf把datadir寫好然后執(zhí)行mysqld --initialize --usermysqlMySQL會讀取配置文件里的datadir參數(shù)在指定路徑下完成初始化。這里有個細(xì)節(jié)初始化完成后會生成一個臨時的root密碼記錄在錯誤日志里一般在/var/log/mysqld.log或者你自己指定的日志文件第一次登錄時要先用這個臨時密碼然后立刻修改密碼。grep temporary password /var/log/mysqld.log mysql -uroot -p ALTER USER rootlocalhost IDENTIFIED BY 你的新密碼;還有一個小知識MySQL 8.0的--initialize默認(rèn)生成隨機(jī)密碼如果想讓root賬號初始為空密碼僅限本地測試環(huán)境可以用--initialize-insecure參數(shù)。但生產(chǎn)環(huán)境千萬別這么玩空密碼的數(shù)據(jù)庫在公網(wǎng)上分分鐘被掃到。4.5 Windows環(huán)境下的目錄修改有什么不同Windows下修改MySQL數(shù)據(jù)目錄核心思路和Linux一樣但有一些Windows特有的坑需要注意。默認(rèn)數(shù)據(jù)目錄在C:\ProgramData\MySQL\MySQL Server 8.0\Data修改步驟如下以管理員身份打開命令提示符停止MySQL服務(wù)net stop MySQL80把Data目錄整體拷貝到新位置比如D:\MySQLData\Data。注意這里要完整拷貝包括隱藏文件。打開C:\ProgramData\MySQL\MySQL Server 8.0\my.ini修改datadir為D:/MySQLData/Data。注意Windows下配置路徑可以用正斜杠或者雙反斜杠單反斜杠會被解析成轉(zhuǎn)義字符容易出錯。還有個Windows特有的坑MySQL服務(wù)注冊時的啟動參數(shù)里可能帶著--datadir這個優(yōu)先級高于配置文件。需要用regedit打開注冊表找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MySQL80檢查ImagePath的值如果帶--datadir參數(shù)就刪掉或者改成新路徑。不然你改了my.ini服務(wù)啟動時還是會用注冊表里的舊路徑。重啟服務(wù)net start MySQL805. 常見問題與排查技巧實(shí)錄5.1 服務(wù)啟動失敗報錯信息怎么看修改數(shù)據(jù)目錄后最常見的失敗場景就是服務(wù)起不來錯誤日志里出現(xiàn)類似這樣的信息[ERROR] MySQL: Cant find error-message file /usr/share/mysql/errmsg.sys [ERROR] Aborting或者是[ERROR] InnoDB: The data directory /data/mysql doesnt exist or is not writable遇到這類問題不要慌先看錯誤日志日志路徑一般在/var/log/mysql/mysqld.log或者/var/log/mysqld.log也可以手動指定--log-error參數(shù)來明確日志路徑?;九挪轫樞蚴切履夸浭欠翊嬖跈?quán)限是否正確ll /data看下屬主是不是mysqlSELinux是否攔截getenforce臨時設(shè)為permissive再啟動試試配置文件是否被正確讀取mysqld --print-defaults可以看到當(dāng)前生效的配置磁盤空間是否足夠df -h確認(rèn)5.2 遷移后無法本地連接socket路徑問題還有一次你在遷移后遇到ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock這類報錯。這個報錯的本質(zhì)是命令行客戶端默認(rèn)通過socket文件連接本地MySQL而socket文件路徑變了因?yàn)閐atadir變了socket默認(rèn)生成在數(shù)據(jù)目錄下客戶端還是按老路徑去找自然找不到。解決辦法是在/etc/my.cnf的[client]段也寫上對應(yīng)的socket路徑[client] socket/data/mysql/mysql.sock這樣你在命令行執(zhí)行mysql -uroot -p時客戶端會自動使用配置里的socket路徑連接不會再去老地方傻找了。5.3 遷移后無法遠(yuǎn)程連接網(wǎng)絡(luò)層的坑有個問題容易被忽略——改了socket和datadir之后如果恰好你的MySQL沒有開啟網(wǎng)絡(luò)監(jiān)聽默認(rèn)情況下bind-address127.0.0.1或者你之前一直通過socket連接現(xiàn)在換了一臺機(jī)器想遠(yuǎn)程連數(shù)據(jù)庫你會發(fā)現(xiàn)怎么都連不上。這個時候可以先確認(rèn)SHOW VARIABLES LIKE bind_address; SHOW VARIABLES LIKE port;如果bind_address是127.0.0.1說明MySQL只監(jiān)聽本地回環(huán)地址外部機(jī)器自然連不上。需要改成0.0.0.0監(jiān)聽所有網(wǎng)卡或者指定內(nèi)網(wǎng)IP同時注意配置防火墻放行3306端口。這個和目錄遷移沒有直接關(guān)系但往往在遷移配置大調(diào)整時被無辜牽連進(jìn)來排查起來比較迷惑。5.4 常見問題速查表我把改目錄過程中頻率最高的幾類問題整理成了一個速查表方便你遇到問題時快速對照現(xiàn)象可能原因排查/解決辦法啟動報Permission denied新目錄屬主不是mysqlchown -R mysql:mysql /data/mysql啟動報Cant find error-message file數(shù)據(jù)不完整缺少初始文件確認(rèn)是否做了完整拷貝新裝環(huán)境先初始化啟動報Directory doesnt exist目錄未創(chuàng)建或路徑寫錯確認(rèn)目錄存在、配置無拼寫錯誤本地連接報Cant connect through socketsocket路徑變了客戶端還用舊路徑在[client]段同步socket路徑服務(wù)啟動了但連不上遠(yuǎn)程bind_address只監(jiān)聽本地修改bind_address并放行防火墻啟動成功但數(shù)據(jù)“丟了”拷貝不完整或目錄搞錯停止服務(wù)核對原目錄文件完整性遷移后部分表損壞拷貝過程中服務(wù)未完全停止遷移前務(wù)必systemctl stop mysqld并確認(rèn)無殘留進(jìn)程5.5 獨(dú)家避坑心得三個我后來才悟到的細(xì)節(jié)細(xì)節(jié)一遷移前一定要做備份。這不是廢話我見過不止一個人在遷移過程中因?yàn)檎`操作刪除了原目錄然后新目錄又不完整最后只能靠冷備份恢復(fù)。在任何數(shù)據(jù)目錄操作前至少執(zhí)行一次mysqldump --all-databases把邏輯備份導(dǎo)出或者直接打個壓縮包放在別的機(jī)器上。數(shù)據(jù)量大的環(huán)境這步該做還得做寧可多花十分鐘備份也不要拿數(shù)據(jù)去賭。細(xì)節(jié)二MySQL 8.0.30之后的redo log結(jié)構(gòu)變化。如果你是從老版本學(xué)來的遷移經(jīng)驗(yàn)知道要拷貝ib_logfile0和ib_logfile1但新版本里這兩個文件已經(jīng)不存在了取而代之的是#innodb_redo目錄??綌?shù)據(jù)時看到?jīng)]有這些文件不用慌張直接把整個目錄完整拷過去就行不要自己動里面的文件。細(xì)節(jié)三改完配置后先別急著刪除原目錄。我的習(xí)慣是新目錄啟動正常、數(shù)據(jù)驗(yàn)證通過、業(yè)務(wù)跑了一兩天穩(wěn)定之后再考慮清理原目錄。這樣可以給你留一條隨時回滾的退路。畢竟生產(chǎn)環(huán)境最怕的就是回不了頭。6. 驗(yàn)證與收尾確保遷移萬無一失數(shù)據(jù)目錄遷移完、服務(wù)也啟動成功之后不要急著把原目錄刪了先做一輪完整的功能驗(yàn)證。順序如下登錄MySQL執(zhí)行基礎(chǔ)查詢SELECT * FROM mysql.user LIMIT 5; USE sys; SHOW TABLES;確保系統(tǒng)庫正常沒有報錯。找一個業(yè)務(wù)庫執(zhí)行一次全表掃描和一次索引查詢對比遷移前后查詢結(jié)果是否一致。如果有需要可以跑一個CHECK TABLECHECK TABLE 你的表名;檢查binlog、錯誤日志、慢查詢?nèi)罩臼欠裾懭胄履夸?。有些環(huán)境把日志目錄也單獨(dú)指定了要確認(rèn)日志文件不是還在老目錄。確認(rèn)數(shù)據(jù)完整性對比記錄數(shù)mysql -uroot -p -e SELECT table_schema, SUM(table_rows) FROM information_schema.tables GROUP BY table_schema;以上操作都完成后遷移才算真正結(jié)束。最后再啰嗦一句無論你多熟練任何涉及數(shù)據(jù)目錄的操作都先把備份做了這既是職業(yè)素養(yǎng)也是對自己和團(tuán)隊負(fù)責(zé)。