指南:從 root 認(rèn)證到遠(yuǎn)程訪問)
做運維這幾年在能在 Ubuntu 22.04 上裝 MySQL 的機會沒有一百也有八十次了。我真正把這套流程吃透是在連續(xù)三次幫別人收拾裝完就跑不起來的服務(wù)器之后——那幾臺機器都是系統(tǒng)剛裝好MySQL 安裝過程毫無波瀾結(jié)果要么 root 密碼怎么試都進(jìn)不去要么遠(yuǎn)程連不上要么建好的表插進(jìn)去的中文全是問號。問題從來不在安裝命令本身而在安裝之后那一堆需要懂原理才能做對的配置。這篇就寫我在 ubuntu22.04 上安裝和配置 mysql 的完整記錄從 apt 安裝、root 認(rèn)證、字符集、時區(qū)、遠(yuǎn)程訪問到幾個我真實踩過的坑整理成一份可以直接照著做的實操教程。如果你手頭有一臺剛裝好的 Ubuntu 22.04 服務(wù)器或者想在本地虛擬機里搭一套 MySQL 環(huán)境做開發(fā)測試照著這篇文章走完基本不會出大問題。1. 動手之前先摸清底細(xì)版本、路線和系統(tǒng)狀態(tài)1.1 Ubuntu 22.04 官源里的 MySQL 8.0 到底能不能用Ubuntu 22.04 的官方軟件源默認(rèn)提供 MySQL 8.0.x注意不是 5.7也不是 MariaDB。我見過不少人在這一步就猶豫覺得官方源版本不夠新非要去裝 8.2、8.3 之類的版本。我的建議是除非有明確需求否則直接用發(fā)行版?zhèn)}庫里的版本。原因是 Ubuntu 打包的 MySQL 會跟著系統(tǒng)一起做測試和 systemd 服務(wù)腳本、AppArmor 配置、日志輪轉(zhuǎn)策略都是配套好的。裝完以后服務(wù)怎么啟動、日志寫到哪、權(quán)限策略長什么樣全部是開箱即用的狀態(tài)。從官方 APT 倉庫裝的版本可能確實更新但你需要自己維護(hù)源地址和 GPG 密鑰還要承受升級時配置差異帶來的風(fēng)險。確認(rèn)版本信息很簡單安裝之前先執(zhí)行sudo apt update apt-cache policy mysql-server裝完以后看實際版本mysql --version我手頭一臺服務(wù)器裝完以后輸出的是mysql Ver 8.0.35-0ubuntu0.22.04.1作為長期運行的數(shù)據(jù)庫服務(wù)器這個版本完全夠用。1.2 三種安裝方式怎么選apt、官方 APT 倉庫、源碼編譯把三種方式擺在一起對比會更直觀安裝方式優(yōu)點缺點適合場景apt發(fā)行版源最省事與系統(tǒng)集成好升級隨系統(tǒng)大版本走小版本更新滯后絕大多數(shù)生產(chǎn)與開發(fā)環(huán)境官方 APT 倉庫版本新補丁及時安全修復(fù)快需要自己配源、管密鑰升級時可能有配置差異對版本有強需求或合規(guī)要求高的環(huán)境源碼編譯定制化強可以自定義編譯參數(shù)編譯慢、依賴多、后期維護(hù)成本極高極少見通常是被迫選擇普通項目用第一種就夠了。即使你真的需要新版本我也建議走官方 APT 倉庫而不是源碼編譯。源碼編譯 MySQL 的坑實在太多依賴庫版本沖突、Boost 版本要求、編譯參數(shù)錯誤導(dǎo)致性能下降而且后續(xù)每次更新都要重新編譯一遍沒有任何運維收益。1.3 準(zhǔn)備階段必做的三件小事安裝之前我習(xí)慣順手做三件事sudo apt update df -h sudo ss -tlnp | grep 3306第一件保證軟件源索引是新的避免 apt 拉到過時的元數(shù)據(jù)第二件確認(rèn)磁盤還有充足空間MySQL 8.0 安裝后數(shù)據(jù)目錄加上日志至少預(yù)留幾個 GB磁盤寫滿會導(dǎo)致初始化失敗第三件確認(rèn) 3306 端口還沒被占用如果機器上已經(jīng)跑了別的服務(wù)占用這個端口后續(xù)啟動會直接沖突。這些檢查聽起來很基礎(chǔ)但我每次裝都做節(jié)省了不少裝完莫名其妙起不來的排查時間。尤其是ss那條曾經(jīng)救過我一次——那臺機器上有個殘留的容器端口映射占著 3306安裝一切正常服務(wù)就是起不來提前確認(rèn)就能一眼定位。2. 從零到跑起來apt 裝完后要做的幾件事2.1 一條命令裝完但裝完要看的不只是安裝成功實際安裝只需要一條命令sudo apt install -y mysql-server執(zhí)行過程中 apt 會拉取 mysql-server、mysql-client 以及一系列運行時依賴。如果系統(tǒng)里之前殘留過別的 MySQL 相關(guān)包apt 可能會提示你是否替換這時建議看清楚再回車不要一路 Y。裝完以后第一件事不是急著建庫而是看一眼服務(wù)狀態(tài)sudo systemctl status mysql正常狀態(tài)是active (running)。如果這里就報錯直接跳到文末第 6 節(jié)看排查路線不要繼續(xù)往下走后面所有操作都依賴一個能正常啟動的 MySQL 實例。2.2 讓 MySQL 開機自啟并確認(rèn) 3306Ubuntu 的 mysql-server 包在安裝時會自動把 MySQL 注冊為 systemd 服務(wù)。這里建議主動執(zhí)行一次sudo systemctl enable mysql sudo systemctl start mysql然后確認(rèn)監(jiān)聽端口和開機自啟狀態(tài)sudo ss -tlnp | grep 3306 sudo systemctl is-enabled mysql兩條輸出分別說明 3306 端口正在被 mysqld 監(jiān)聽、MySQL 服務(wù)已加入開機自啟列表。這里有個重點默認(rèn)情況下 MySQL 只監(jiān)聽127.0.0.1即只允許本機連接。這個特性會在第 5 節(jié)詳細(xì)展開你現(xiàn)在只需要記住裝完默認(rèn)狀態(tài)是本機可用、外部不可達(dá)。2.3 初次進(jìn)入 MySQL 后建議做的事現(xiàn)在用無密碼方式登錄sudo mysql -u root看到mysql提示符就代表一切正常。建議此時至少執(zhí)行兩條命令SHOW DATABASES; SELECT user, host, plugin FROM mysql.user;第一條會列出默認(rèn)數(shù)據(jù)庫information_schema、mysql、performance_schema、sys。第二條用來查看用戶列表重點關(guān)注rootlocalhost那一行對應(yīng)的plugin值后面第 3 節(jié)會用到。這里順便多說一句如果之后你用某個開發(fā)框架初始化數(shù)據(jù)庫時報錯無法創(chuàng)建數(shù)據(jù)庫多半就是賬號權(quán)限不夠。你需要回到這里用 root 執(zhí)行GRANT給業(yè)務(wù)賬號授權(quán)而不是去修改 MySQL 的底層權(quán)限表——直接改 mysql.user 表是反模式很容易把系統(tǒng)搞壞。3. root 賬號的認(rèn)證機制為什么默認(rèn)只能用 sudo mysql 進(jìn)3.1 auth_socket 的工作方式和設(shè)計初衷如果你剛裝完就執(zhí)行mysql -u root -p會被告知密碼錯誤。這不代表你漏設(shè)了密碼而是 rootlocalhost 用的認(rèn)證插件是auth_socket。auth_socket不校驗密碼它只校驗兩件事連接方式是不是本地 Unix socket以及發(fā)起連接的進(jìn)程是不是以系統(tǒng) root 身份運行。換句話說只要你能拿到系統(tǒng)的 root 權(quán)限在本地執(zhí)行sudo mysql就能直接進(jìn) MySQL root密碼形同虛設(shè)。這個設(shè)計有它的道理Ubuntu 的思路是MySQL 的 root 應(yīng)該由系統(tǒng)的 root 來管你要用 MySQL 做業(yè)務(wù)就去建自己的業(yè)務(wù)賬號。對不少開發(fā)者來說這有點繞把 root 改成密碼登錄更順手所以接下來要做的就是改認(rèn)證方式。3.2 改成密碼登錄的正確姿勢用前面說的sudo mysql -u root方式進(jìn)入后執(zhí)行ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY 你的強密碼;或者更簡潔一點ALTER USER rootlocalhost IDENTIFIED BY 你的強密碼;默認(rèn)認(rèn)證插件是caching_sha2_password這是 MySQL 8.0 的默認(rèn)項安全性比老式的mysql_native_password更高。不過要注意如果以后有一個非常老的客戶端連過來不認(rèn)這個插件可以暫時給對應(yīng)賬號指定mysql_native_password但這個插件在 8.0.34 開始已經(jīng)被標(biāo)記為廢棄只能作為過渡手段。改完之后有一件事必須記住以后 root 也需要密碼才能進(jìn)入包括本機sudo mysql也無法直接繞過。我見過好幾個同事改完密碼隨手一放過兩天忘了最后只能走skip-grant-tables的恢復(fù)流程非常折騰。所以改之前先確認(rèn)密碼已經(jīng)記在安全的地方。3.3 mysql_secure_installation 加固流程逐項說明改完密碼后建議立刻跑一遍官方加固腳本sudo mysql_secure_installation腳本會依次詢問幾個問題我按推薦答案逐項說是否配置密碼強度校驗組件VALIDATE PASSWORD COMPONENT——開發(fā)環(huán)境可以選 N正式環(huán)境建議選 Y。選 Y 后還會讓你選強度等級 0/1/2一般選 1 級就夠。是否為 root 設(shè)置密碼——如果你在前面已經(jīng)改過這里會要求輸入當(dāng)前密碼。是否移除匿名用戶——選 Y。殘留的匿名賬號會帶來權(quán)限混亂具體見第 6 節(jié)。是否禁止 root 遠(yuǎn)程登錄——選 Y。遠(yuǎn)程連接要單獨建用戶不要開 root 遠(yuǎn)程。是否刪除 test 數(shù)據(jù)庫——選 Y。默認(rèn) test 庫沒有任何權(quán)限限制留著沒有好處。是否立即刷新權(quán)限表——選 Y。這一套流程跑完一個相對干凈的基礎(chǔ)環(huán)境就出來了。很多人裝完 MySQL 直接開始建庫跳過加固腳本后面遇到的很多詭異問題其實都能在這個腳本里避免。4. 字符集、排序規(guī)則和時區(qū)讓數(shù)據(jù)庫說中文4.1 為什么是 utf8mb4 而不是 utf8MySQL 里的utf8其實不是大家理解的 UTF-8它最多只支持 3 字節(jié)字符官方文檔叫它utf8mb3。表情符號、部分生僻漢字、擴展區(qū)字符都需要 4 字節(jié)編碼存進(jìn) utf8 字段要么報錯要么變成問號。utf8mb4才是完整的 UTF-8 編碼。MySQL 8.0 默認(rèn)字符集已經(jīng)改成 utf8mb4但如果你是從舊版本遷移過來的環(huán)境服務(wù)器變量很可能還停留在舊值。裝完以后用這條 SQL 看一眼SHOW VARIABLES LIKE character_set_server;如果看到的不是 utf8mb4就要在配置層面統(tǒng)一改掉。排序規(guī)則方面8.0 默認(rèn)是utf8mb4_0900_ai_ci我自己的習(xí)慣是新環(huán)境直接用默認(rèn)值但如果你要從 5.7 遷移庫可以用utf8mb4_unicode_ci它在舊版本里就存在兼容性更好。4.2 修改配置文件讓全局默認(rèn)生效Ubuntu 上 MySQL 的主配置是/etc/mysql/my.cnf它通過 include 加載/etc/mysql/conf.d/和/etc/mysql/mysql.conf.d/下的文件。服務(wù)端配置真正存放的位置是/etc/mysql/mysql.conf.d/mysqld.cnf。編輯這個文件在[mysqld]段下加入[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci然后重啟sudo systemctl restart mysql重啟后再次確認(rèn)SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%;這里有個細(xì)節(jié)非常容易踩服務(wù)端字符集改了但客戶端連接時的character_set_client可能還是舊的。為了讓 mysql 命令行客戶端也統(tǒng)一可以新建一個/etc/mysql/conf.d/charset.cnf[client] default-character-set utf8mb4服務(wù)端、數(shù)據(jù)庫、客戶端三層字符集全部統(tǒng)一后中文亂碼問題基本就根治了。我之前幫人排查過一個亂碼問題服務(wù)端和庫都改成 utf8mb4 了亂碼依舊最后發(fā)現(xiàn)就是客戶端連接參數(shù)沒帶上字符集設(shè)置——服務(wù)端改得再對客戶端以 latin1 連進(jìn)來照樣是問號。4.3 已經(jīng)建好的老庫老表怎么補救如果你在改配置之前就已經(jīng)建庫建表光改全局配置不會影響已有對象庫和表要單獨改ALTER DATABASE 你的庫名 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE 你的表名 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意這里的區(qū)別CONVERT TO會重建表并轉(zhuǎn)換已有列的數(shù)據(jù)大表可能需要很長時間還會產(chǎn)生鎖競爭盡量放在維護(hù)窗口執(zhí)行。如果只想改表的默認(rèn)字符集、不動已有列的數(shù)據(jù)用ALTER TABLE ... DEFAULT CHARACTER SET utf8mb4就行。{% hint styleinfo %} 判斷一個表當(dāng)前到底是什么字符集用SHOW CREATE TABLE 表名;輸出的DEFAULT CHARSET位置就是答案。 {% endhint %}4.4 時區(qū)設(shè)置比你想的更影響業(yè)務(wù)代碼默認(rèn)情況下 MySQL 8.0 的時區(qū)跟隨系統(tǒng)的時區(qū)設(shè)置。如果服務(wù)器是 UTC 時區(qū)業(yè)務(wù)在北京時間上午 9 點寫入數(shù)據(jù)SELECT NOW()出來的時間會比預(yù)期晚 8 小時這個坑非常隱蔽——應(yīng)用日志看起來時間正常數(shù)據(jù)庫里查出來卻對不上。處理辦法是在[mysqld]段加入default-time-zone 08:00重啟后用SELECT NOW();驗證。如果想用帶名字的時區(qū)比如Asia/Shanghai需要先把系統(tǒng)時區(qū)表導(dǎo)入 MySQLmysql_tzinfo_to_sql /usr/share/zoneinfo | sudo mysql mysql這條命令在生產(chǎn)環(huán)境要謹(jǐn)慎執(zhí)行因為它會把大量時區(qū)數(shù)據(jù)寫進(jìn) mysql 系統(tǒng)庫。多數(shù)場景下直接寫固定偏移量就足夠了簡單直接還沒有額外開銷。5. 遠(yuǎn)程訪問的完整鏈路bind-address、防火墻和專用賬號5.1 默認(rèn)只聽 127.0.0.1到底為什么回到前面說的默認(rèn)只監(jiān)聽本地。打開/etc/mysql/mysql.conf.d/mysqld.cnf能看到這一行bind-address 127.0.0.1bind-address決定 mysqld 監(jiān)聽哪個網(wǎng)絡(luò)接口。127.0.0.1表示只允許本機回環(huán)接口連接安全性最好但其他機器完全無法訪問。需要遠(yuǎn)程連接時有兩種改法改成0.0.0.0監(jiān)聽所有網(wǎng)卡或者改成內(nèi)網(wǎng) IP比如10.0.0.8只監(jiān)聽特定網(wǎng)卡。我自己的習(xí)慣是盡量用具體 IP避免把服務(wù)暴露到不必要的網(wǎng)段。改完同樣要重啟然后用ss驗證sudo ss -tlnp | grep 33065.2 修改監(jiān)聽并放行防火墻監(jiān)聽地址改完后還要過防火墻這一關(guān)。Ubuntu 自帶 UFW執(zhí)行sudo ufw allow 3306/tcp sudo ufw status如果部署在云服務(wù)器上記得同時檢查安全組的入方向規(guī)則。我見過很多案例本地防火墻開了、云安全組沒放行導(dǎo)致從公網(wǎng)怎么都連不上。兩地都要檢查缺一個就通不了。5.3 創(chuàng)建業(yè)務(wù)專用賬號別把 root 暴露出去遠(yuǎn)程連接我強烈不建議直接用 root哪怕只在公司內(nèi)網(wǎng)使用。正確做法是建一個只擁有所需權(quán)限的業(yè)務(wù)賬號CREATE USER app_user192.168.1.% IDENTIFIED BY 另一個強密碼; GRANT SELECT, INSERT, UPDATE, DELETE ON 你的庫.* TO app_user192.168.1.%; FLUSH PRIVILEGES;賬號名后面的主機部分是重點。192.168.1.%表示只允許這個網(wǎng)段連接10.0.0.5精確匹配單個 IP%表示任意主機能用但也最危險。其實在 MySQL 8.0 里CREATE USER和GRANT執(zhí)行后權(quán)限會自動生效不需要FLUSH PRIVILEGES——這個命令只有在你直接修改了 mysql.user 表時才需要。寫了當(dāng)然無害但也別指望它能解決權(quán)限不生效的問題。還有一個隱藏坑如果開啟了skip_name_resolveMySQL 不會反查客戶端域名所有按主機名匹配的賬號都會失效。所以生產(chǎn)環(huán)境統(tǒng)一用 IP 段做主機匹配別寫域名。最后說一個和 MySQL 8.0 有關(guān)的小細(xì)節(jié)默認(rèn)還開啟了 33060 端口供 MySQL Shell 的 X Protocol 使用。如果你用 MySQL Shell 遠(yuǎn)程管理要記得在防火墻放行這個端口否則會出現(xiàn)3306 能連但 Shell 連不上的詭異現(xiàn)象。6. 部署后踩過的坑四個高頻問題的排查實錄6.1 AppArmor 攔住了自定義數(shù)據(jù)目錄有人不想把數(shù)據(jù)放在/var/lib/mysql想挪到/data/mysql。改了datadir之后啟動服務(wù)會直接失敗錯誤日志里寫著權(quán)限不足。這不是文件權(quán)限問題而是 AppArmor 在攔截。Ubuntu 自帶一套針對 mysqld 的 AppArmor 策略只允許 mysqld 訪問/var/lib/mysql等預(yù)設(shè)路徑自定義目錄不在白名單里。解決思路有兩條。第一條是修改 AppArmor 配置文件編輯/etc/apparmor.d/usr.sbin.mysqld加上一行alias /var/lib/mysql/ - /data/mysql/,保存后重載sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld第二條是我現(xiàn)在更推薦的做法不改 datadir而是在/var/lib/mysql路徑上掛載一塊新磁盤或分區(qū)。這樣既不會觸發(fā) AppArmor也完全不需要改 MySQL 配置后續(xù)磁盤擴容也更靈活。數(shù)據(jù)目錄遷移這件事能不碰配置就別碰配置。6.2 老業(yè)務(wù)連不上 8.0caching_sha2_password 兼容性處理MySQL 8.0 默認(rèn)認(rèn)證插件改成caching_sha2_password之后客戶端驅(qū)動跟不上就會出現(xiàn)這類報錯Authentication plugin caching_sha2_password cannot be loaded。常見于比較老的 PHP mysqlnd、舊版 JDBC 驅(qū)動、某些低版本的 Go 驅(qū)動。最推薦的解法是升級客戶端驅(qū)動庫這比在數(shù)據(jù)庫層面做兼容要徹底得多。如果業(yè)務(wù)暫時不能動可以給這個老客戶端單獨建一個兼容賬號過渡CREATE USER legacy_app% IDENTIFIED WITH mysql_native_password BY 密碼; GRANT 需要的權(quán)限 ON 你的庫.* TO legacy_app%;注意mysql_native_password在 8.0.34 開始被標(biāo)記為廢棄只適合作為過渡方案別把它當(dāng)成長期配置。如果你發(fā)現(xiàn)新裝的 MySQL 里caching_sha2_password連不上的問題反復(fù)出現(xiàn)優(yōu)先從客戶端版本入手而不是把服務(wù)端認(rèn)證方式降級。6.3 anonymous user 導(dǎo)致 root 密碼白設(shè)有個很隱蔽的問題root 密碼明明改對了從本機用密碼登錄仍然被拒絕。查了一圈發(fā)現(xiàn)問題出在殘留的匿名賬號上。MySQL 匹配用戶時有一套優(yōu)先級規(guī)則匿名用戶在某些連接場景下會先被匹配到連接就被導(dǎo)向了一個你不知道的賬號自然提示 access denied。檢查方法SELECT user, host, plugin FROM mysql.user;看到 user 為空字符串的行就是匿名用戶。直接刪掉DROP USER localhost;這也解釋了為什么mysql_secure_installation里那個是否移除匿名用戶的問題值得選 Y。跳過這一步的人早晚要在這上面吃一次虧而且排查成本遠(yuǎn)高于按一次 Y 的成本。6.4 服務(wù)起不來的通用排查路線最后列一個通用的排查流程。MySQL 服務(wù)起不來時第一步永遠(yuǎn)不是改配置而是看日志systemctl status mysql journalctl -u mysql --no-pager -n 50 tail -n 100 /var/log/mysql/error.logUbuntu 上 MySQL 的錯誤日志默認(rèn)在/var/log/mysql/error.log但具體路徑要看log_error參數(shù)指向哪里。日志位置不對可以用SHOW VARIABLES LIKE log_error;確認(rèn)。常見原因和處理動作見下表報錯/現(xiàn)象最常見的原因優(yōu)先處理動作Job for mysql.service failed配置語法錯誤、datadir 權(quán)限不對看 error.log檢查/var/lib/mysql屬主是否為 mysql:mysqlCant connect to local MySQL server through socket服務(wù)沒啟動或 socket 目錄不可寫重啟服務(wù)確認(rèn)/var/run/mysqld存在且屬主正確The table ... is full磁盤滿df -h查看清理或擴容AppArmor ... denied自定義路徑未加入策略按 6.1 的方式處理error.log 里通常已經(jīng)把真正的原因?qū)懬宄撕芏鄷r候只是一條配置不對或者目錄權(quán)限問題。把日志完整看完再動手比瞎試一通高效太多。說一句題外話我現(xiàn)在每裝一臺新機器的 MySQL都會在改完配置后先把/etc/mysql下所有 .cnf 文件備份一份文件名帶上日期。這套動作花不了半分鐘但后期排查配置漂移問題時能省掉不少冤枉時間。如果你也要在生產(chǎn)環(huán)境部署建議把這篇里涉及的關(guān)鍵 SQL 命令整理成一份初始化腳本裝完直接跑一遍比手工一條條敲可靠得多。