“系统盘又满了”这句话做运维和开发的朋友应该都不陌生。我自己手上就有几台 Ubuntu 24 LTS 的云主机MySQL 8 装好后默认把所有数据、binlog 都丢在/var/lib/mysql系统盘只有 30G 的话跑不到两周准报警。问题其实不在 MySQL 本身而在安装时没人提前去想“数据库目录要不要指到独立数据盘”。这篇文章就是我在 Ubuntu 24 上把 MySQL 8 的数据目录、日志目录、binlog 目录整体配置到指定盘位的完整记录中间包括了 AppArmor 权限、systemd 服务、目录权限这些连环坑。准备在云服务器上装 MySQL、系统盘空间紧张、或者想把数据库放到独立挂载目录的同学可以直接照着走。1. 动盘之前先摸清布局MySQL 8 默认目录到底有哪些改目录这种事最忌讳上来就改配置。你得先知道 MySQL 8 在 Ubuntu 24 下默认把东西放在哪才能判断哪些目录值得搬、哪些动了纯属给自己找麻烦。1.1 Ubuntu 24 安装后 MySQL 8 的默认目录结构Ubuntu 24 自带的 mysql-server 包实际版本是 MySQL 8.0.x配置路径和早期 5.7 时代差别不大主配置入口在/etc/mysql/my.cnf。这个文件本身基本不写实质性内容靠的是 include 机制把配置分散到/etc/mysql/conf.d/和/etc/mysql/mysql.conf.d/两个目录真正的服务端配置集中在mysqld.cnf里。我建议你动手前先跑一下这几条命令把当前路径列清楚sudo my_print_defaults mysqld | grep -E datadir|socket|pid-file|log df -hT /var/lib/mysql ls -ld /var/lib/mysql /var/run/mysqld /var/log/mysql我把默认目录整理成了一张表后面做迁移规划时直接对着看目录/文件默认位置用途datadir/var/lib/mysql数据文件、系统表空间、undo 日志、默认 binlog 位置socket/var/run/mysqld/mysqld.sock本地 Socket 连接入口pid-file/var/run/mysqld/mysqld.pid进程 PID 锁文件log-error/var/log/mysql/error.log错误日志排障第一入口tmpdir/tmp磁盘临时表、排序文件、大事务临时文件slow_query_log_file默认关闭路径随 datadir慢查询日志plugin_dir/usr/lib/mysql/plugin插件库不建议迁移这里有个容易忽略的细节binlog 在 MySQL 8 里如果没单独指定log_bin路径它会直接写在 datadir 下面文件名类似mysql-bin.000001。换句话说你以为只动了数据目录其实 binlog 也跟着走了。后面第 4 节我会单独讲怎么把 binlog 拆到独立目录。1.2 哪些目录值得搬哪些千万别动我自己总结了三条原则迁移前先想明白它们后面能少踩很多坑。第一datadir 是必迁项。如果你的系统盘和数据盘是分开的或者根分区只有几十 G把数据留在/var/lib/mysql迟早出问题。binlog、undo log、redo log 都在里面增长一个活跃业务的 binlog 一天几个 G 是很正常的事。把 datadir 迁走相当于把 MySQL 读写压力从系统盘转移到了数据盘既解决空间问题也能让系统盘本身更干净。第二socket 和 pid 文件不要动。这俩默认都在/var/run/mysqld属于内存文件系统重启会自动创建。很多本地运维脚本、备份工具、监控采集器都默认从这个路径连接 MySQL我见过有人非要把 socket 指到数据目录结果 phpMyAdmin、mysqldump 全要跟着改连接参数纯粹是自找麻烦。除非你有非常特殊的多实例隔离需求否则保持默认。第三日志目录可以动但要连 logrotate 一起动。Ubuntu 的 mysql-server 包默认带了/etc/logrotate.d/mysql-server里面对/var/log/mysql/*.log做按周切割。如果你把 error.log、慢查询日志迁到/data下却忘了改 logrotate日志文件就别想自动切割了会越来越大最后把数据盘反而撑满。2. 干净安装与初始化检查动手迁移前必须确认的三件事很多教程会直接让你装完 MySQL 就改目录但实际生产环境里安装顺序决定了后面要处理多少麻烦。我建议在安装阶段就把目标盘准备好然后按下面三步来。2.1 在 Ubuntu 24 上通过 apt 安装正式版 MySQL 8先说明一下Ubuntu 24 LTS 默认软件源里的 mysql-server 是 Oracle 的 MySQL 8.0不是 MariaDB。这一点和 Ubuntu 20.04 时代容易搞混当时某些版本默认源会指向 MariaDB。装之前可以先确认一下sudo apt update sudo apt install mysql-server -y mysql -V如果输出里有Distrib 8.0.x就对了。装完之后服务名是mysql不是mysqld所以启动、重启、看状态都要用systemctl status mysql --no-pager sudo systemctl enable mysql这里有个小提示国内服务器如果 apt 源很慢可以先换回你常用且信任的镜像源再做 update不然等 mysql-server 下载耗掉十分钟很正常。换源后记得把mysql-server几个关键包版本锁一下避免后面意外升级导致配置被覆盖第 5 节会专门讲这个。2.2 确认目标盘、文件系统与挂载参数迁移前最重要的步骤不是复制数据而是确认你要把数据放到哪。我习惯用lsblk先看盘符结构lsblk -f df -hT假设你要把 MySQL 迁到/data这一步必须确认两件事第一/data对应的分区是不是独立数据盘第二文件系统是否合适。MySQL 8 对文件系统本身没有强制要求但生产环境我更推荐 ext4 或者 xfs并且挂载参数里加上noatime避免读数据时频繁更新 atime 产生额外 IOsudo mkdir -p /data/mysql sudo chown -R mysql:mysql /data/mysql网上很多教程会让你直接chown mysql:mysql但容易忽略父目录的权限。比如/data本身如果是 755mysql用户能进但如果/data是 700 而属主是 root那即使子目录属主对了MySQL 也进不去。最稳的做法是让 mysql 用户对路径上每一级目录都有rx权限或者直接把目标根目录一并处理。如果你是要在 Windows 的 WSL 里做 Ubuntu 24 MySQL 8 实验有一点和真机不一样WSL 默认不启用 systemd服务要用service mysql start启动AppArmor 默认基本不生效所以迁移时反倒更简单。但生产环境还是以完整 Linux 为准。3. 核心操作把 datadir 迁到指定地址的全过程下面这部分是整个迁移过程的关键我会按实际操作的顺序拆开讲。整个过程适合冷迁移也就是说先停服务、再复制数据、最后改配置重启。对 MySQL 8 来说冷迁移是最安全的方式没有一致性问题也不需要开启 GTID 或备份工具配合。3.1 停止服务与冷备这一步最好不要跳停服务这个动作看似简单但生产环境里容易出岔子。比如你一执行systemctl stop mysql某个监控脚本又自动把它拉起来了。保险做法是停完后立刻看状态sudo systemctl stop mysql sudo systemctl status mysql --no-pager | head -5确认服务真的停了再开始做冷备份。我的习惯是先用rsync把整个 datadir 同步到新目录同时保留一份旧目录复制作为回滚点sudo rsync -avzh --progress /var/lib/mysql/ /data/mysql/data/ sudo cp -a /var/lib/mysql /var/lib/mysql.bak.$(date %F)注意第一处/var/lib/mysql/结尾的斜杠它表示复制目录内容而不是把mysql这个目录本身嵌套到目标目录里。如果你写成/var/lib/mysql不带斜杠结果会变成/data/mysql/data/mysql配置一改就会启动失败。为什么推荐rsync而不是cp -a因为这步之后你大概率还要调整权限、或者反复校验复制结果rsync 支持断点续传、可以加--checksum校验一致性而且第一轮复制完成后再跑一遍可以确认两边文件完全一致。备用的cp -a则是为了在本地留一个绝对可信的回滚副本尤其是当目标盘和源盘不是同一个文件系统时cp -a更能保留扩展属性和 ACL。3.2 修改服务配置指向新目录复制完成后接着改配置文件。Ubuntu 24 下 MySQL 的主配置虽然叫/etc/mysql/mysql.conf.d/mysqld.cnf但我不建议直接在这个文件里删除原来的datadir行再添加新行尤其是碰上后续要回滚的情况越改越乱。更好的做法是新建一个独立的配置片段放在mysql.conf.d目录下用字母顺序保证它最后加载sudo tee /etc/mysql/mysql.conf.d/zzz-custom.cnf EOF [mysqld] datadir /data/mysql/data user mysql EOF如果mysqld.cnf里原本已经写了datadir /var/lib/mysql会发生什么是后面加载的小配置文件把它覆盖掉吗严格来说不一定会因为 MySQL 对重复配置项默认取最后一个值而配置文件加载顺序是按文件名排序的。zzz-custom.cnf排在mysqld.cnf后面所以最终生效的是/data/mysql/data。但为了稳妥我还是会把原文件里的datadir行注释掉避免任何工具扫描时看到两份配置影响判断。改完配置先不急着启动用下面命令确认一下最终生效值sudo my_print_defaults mysqld | grep datadir这里输出的--datadir/data/mysql/data才是真正会被 mysqld 读取到的值。如果这里显示的还是旧路径说明你的覆盖文件没有生效去检查 include 路径和文件后缀名。3.3 权限、AppArmor 与启动验证改完配置后最关键的一步来了。在 Ubuntu/Debian 系系统上MySQL 迁移目录后启动失败的九成原因不是没配置到位而是 AppArmor 挡住了新路径。AppArmor 给 mysqld 进程默认只放行了/var/lib/mysql/**之类的路径你把它指向/data/mysql/data进程连读目录的权限都没有。先给新目录设置好属主sudo chown -R mysql:mysql /data/mysql/data sudo chmod 750 /data/mysql/data然后给 AppArmor 增加规则。官方最省事的方法是给路径做别名映射把旧路径“翻译”成新路径sudo sh -c echo alias /var/lib/mysql/ - /data/mysql/data/, /etc/apparmor.d/tunables/alias sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld systemctl reload apparmor我更常用的方法是在 local 配置文件里直接追加规则逻辑更直白sudo tee -a /etc/apparmor.d/local/usr.sbin.mysqld EOF /data/mysql/ r, /data/mysql/data/** rwk, EOF sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld这步做完后再启动 MySQLsudo systemctl start mysql systemctl status mysql --no-pager mysql -uroot -p -e SELECT datadir, version;如果一切正常你会看到datadir返回/data/mysql/data。到了这一步datadir 的迁移其实已经完成一大半了。但我在实际项目中还顺带做了另外几件事——把 binlog 和日志也拆出去不然 datadir 虽然换了盘数据盘照样会被日志吃满。4. 一并处理的兄弟目录binlog、日志和临时空间很多人迁完 datadir 就以为大功告成过两周发现数据盘又满了。原因很简单binlog、慢查询日志、临时表文件都还在老地方或者已经在新 datadir 里继续膨胀。既然已经开了迁移的头不如把这几类目录一次规划好。4.1 binlog 单独落到独立目录MySQL 8 默认会开启 binlog如果你在配置里只写了数据目录迁移binlog 仍然写在 datadir 下。它的增长速度和业务量挂钩而且 binlog 文件默认设置下单个文件大小是 1GB攒满一整套就能吃掉好几个 G。我的做法是在数据盘上单独建一个binlog目录让日志和数据隔离sudo mkdir -p /data/mysql-binlog sudo chown mysql:mysql /data/mysql-binlog然后往zzz-custom.cnf里追加[mysqld] log_bin /data/mysql-binlog/mysql-bin binlog_expire_logs_seconds 604800 max_binlog_size 256M这里的mysql-bin是 binlog 前缀实际生成的文件会叫mysql-bin.000001。为什么建议单独目录因为日常备份、PITR 恢复、从库同步都要读 binlog把日志放在独立目录可以单独给这个目录加磁盘配额、单独做归档策略也不会和数据文件抢 IO。binlog_expire_logs_seconds 604800是保留 7 天实际天数根据你的备份周期来定别拍脑袋设成 0。重新加载配置后你用下面命令验证mysql -uroot -p -e SHOW VARIABLES LIKE log_bin%; SHOW VARIABLES LIKE binlog_expire%;4.2 错误日志和慢查询日志的位置调整Ubuntu 默认错误日志在/var/log/mysql/error.log这个东西如果不挪系统盘还是会被写大。尤其在生产环境一个频繁报错的 MySQL 一天能写几百 MB 的错误日志。慢查询日志如果开启也会在数据目录里越铺越大。我的建议是把它们统一放到/data/mysql-logsudo mkdir -p /data/mysql-log sudo chown mysql:mysql /data/mysql-log配置追加[mysqld] log_error /data/mysql-log/error.log slow_query_log 1 slow_query_log_file /data/mysql-log/slow.log long_query_time 2这里要特别提醒一旦改了log_error原来的/var/log/mysql/error.log里就不再有新内容了排障时先看新路径。同时还要改 logrotate 配置否则日志切割机制就断了。编辑/etc/logrotate.d/mysql-server把所有路径替换成新路径改完可以手动测试一下sudo logrotate -vf /etc/logrotate.d/mysql-server如果你忘了改 logrotate最常见的表现是日志文件越来越大却不切割最终把数据盘写满反而比迁移前更糟。4.3 tmpdir 和 socket 的取舍关于tmpdir我看不少文章都建议给 MySQL 单独设置临时目录这个建议在 IO 密集场景下没毛病。但如果你的/tmp是挂在内存盘tmpfs上或者数据盘本身是普通 HDD那就不一定值得把 tmpdir 迁到数据盘——临时文件放内存或 SSD 上反而更快。如果你的临时表需求很大想单独指定[mysqld] tmpdir /data/mysql-tmp创建后同样要chown mysql:mysql并且加到 AppArmor 放行规则里。但这里有个取舍tmpdir 放在磁盘事务一旦写了大临时表磁盘 IO 会明显升高排序和 group by 操作变慢放在 tmpfs 又会占内存。我的建议是内存充足比如 16G 以上就保持默认/tmp内存紧张再落地到磁盘目录。socket 和 pid 我前面说过不要动。如果你非要多实例隔离那属于另一个话题Ubuntu 24 下更推荐直接上 docker 而不是硬改 socket 路径前者反而省心得多。5. 启动失败排查与回滚方案这些实测遇到的问题迁移 MySQL 目录最让人心累的不是操作本身而是改完后启动失败然后被一串乱糟糟的报错绕晕。这一节我把实际踩过的坑按排查链路整理出来遇到问题直接对号入座。5.1 完整排查链路从 Permission denied 到目录权限假如你按上面的步骤做完systemctl start mysql后服务起不来先别慌。按顺序做这几步第一步看错误日志。注意新路径sudo tail -n 100 /data/mysql-log/error.log如果这一步你还没改 log_error那就看老的/var/log/mysql/error.log。日志里最常出现的几种情况我列个表报错特征原因应对Permission deniedAppArmor 拦截或目录属主不对检查 AppArmor 规则、重新 chown、chmodCant open file或OS errno 13目录权限不足确认 mysql 用户对路径每级目录有 x 权限File /data/... not found (Errcode: 2 - No such file or directory)目录没创建或配置里带了不存在的路径mkdir -p 后重新 chownTable doesnt exist但数据明明在datadir 里原有目录被嵌套复制了检查 rsync 是否带了多余一层目录第二步看 AppArmor 日志。Ubuntu 下 AppArmor 拦截记录一般在dmesg或journalctlsudo dmesg | grep -i apparmor | tail -20 sudo journalctl -u mysql --no-pager | tail -30如果你看到apparmorDENIED字样基本就是规则没有生效。我的处理方式很简单先把规则里新目录加了然后不只是systemctl reload apparmor再多跑一次apparmor_parser -r确保 profile 真的被重新加载了。第三步检查目录权限是不是被“父目录”绑架了。我遇到过一种情况目标盘根目录是 root 且 700 权限mysql 用户根本进不去而所有人都在反复检查/data/mysql/data本身的属主完全忽略了上一级目录。这个问题用一条命令即可验证sudo -u mysql test -r /data/mysql/data echo ok返回ok才说明 mysql 用户真的能读。5.2 AppArmor rules 改完不生效的坑AppArmor 的坑比想象中多。第一种是配置加载顺序。你往/etc/apparmor.d/local/usr.sbin.mysqld里加了规则但加载usr.sbin.mysqld主 profile 时走的还是旧缓存导致看似配置了实际没生效。解决办法是执行sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld sudo systemctl reload apparmor第二条命令反过来再跑一遍双保险。第二种是在老版本里用alias语法没问题但某些环境下 alias 会影响规则路径的匹配顺序导致新路径能访问旧路径也被自动映射反而掩盖了真正的权限问题。因此我更推荐直接给 local 文件添加rwk规则逻辑更可控。这里顺便提一个和迁移无关、但经常和 MySQL 启动一起出现的环境问题如果你是在 VirtualBox 这类虚拟机里装 Ubuntu 24 做 MySQL 实验遇见开机黑屏、卡进度条的情况先别怀疑是 MySQL 或目录迁移造成的。去虚拟机设置里把显存调大、开启 3D 加速磁盘控制器选 SATA基本都能正常进系统。我见过不少人在虚拟机黑屏上花了一上午最后发现和数据库配置半毛钱关系都没有。5.3 升级或重装包时目录配置会不会被打回原形这是很多运维没考虑到的坑apt upgrade mysql-server之后datadir 配置突然被重置服务启动还是找旧路径。其实绝大多数情况下并不会因为 Ubuntu 的配置文件在包升级时会做三方合并你往mysql.conf.d里新增的zzz-custom.cnf不会被删除。真正需要担心的是你直接改了mysqld.cnf原文件升级时又碰上配置文件变更容易冲突甚至被覆盖。所以我在第 3 节才会坚持让你新建一个独立配置文件就是为了隔离这个风险。同理如果你是用官方 tar 包手动安装 MySQL 8网上有时候叫“免安装版”那就别走 AppArmor 这条路了跳过 AppArmor 规则重点检查my.cnf的basedir、datadir路径以及初始化时用的--initialize-insecure参数。手动安装和 apt 包的目录逻辑基本一致但没有 systemd 服务脚本启动方式建议用mysqld_safe测试绕开一堆隐藏依赖。5.4 回滚方案改回来比重新复制快任何迁移操作都必须有回滚预案。我的做法是保留旧目录至少两个星期而且回滚时只改配置不动数据sudo systemctl stop mysql sudo sed -i s|datadir /data/mysql/data|datadir /var/lib/mysql| /etc/mysql/mysql.conf.d/zzz-custom.cnf sudo systemctl start mysql如果旧目录还在这一套配置改回去后再启动服务基本秒开。如果旧目录已经被你删了那就只能从备份恢复成本完全不是一个量级。所以把/var/lib/mysql.bak.*多留几天定期清理别手滑提前删。回滚后别忘了把log_error、slow_query_log_file、log_bin这三个配置也一并还原否则 MySQL 会尝试往/data/mysql-log写日志而 AppArmor 规则还指向老路径照样启动失败。这属于我亲测过的高频翻车点。最后补充几件日常顺手要做的事目录迁移完成不是终点后续运维还有几件小事我顺手写下来供参考。第一迁移后记得把zzz-custom.cnf里的路径和实际目录做一次对应检查最好输出到一份运维文档里。我遇到过同事半年后接手服务器看到datadir指向一个磁盘已快满的挂载点一脸茫然。第二如果 MySQL 要开 binlog 做 PITR确认备份脚本拉取的 binlog 路径是新的/data/mysql-binlog而不是旧 datadir 下的文件。第三所有本地工具、监控脚本如果依赖 socket连接参数不用改但如果它们直接扫描/var/lib/mysql目录做容量统计记得更新路径规则。最后分享一个我自己的操作习惯迁移完成后我会先跑一轮sysbench或者简单的mysqlslap压测确认新目录下 IO 和连接都正常观察一个完整的日志切割周期之后再把旧目录改名成类似/var/lib/mysql.old的归档名而不是直接删除。少删一个目录最多多占几天磁盘多删一个目录遇到问题就真的要重头来过了。
阅读完成 · 觉得有帮助?