Changing the MySQL Data Directory on Linux
My root partition filled up because /var/lib/mysql kept growing, and the fix was a new disk mounted at /data. Changing datadir is the easy part. Then SELinux (CentOS) or AppArmor (Ubuntu) quietly refuses to let mysqld touch the new location, and that's where I lose the evening.
This is what I do on CentOS 7 and Ubuntu 16.04 with MySQL 5.7; MariaDB 10.1 is the same with minor path differences. Have a mounted target filesystem with room to grow, a recent backup (a typo in rsync is the real risk), and a maintenance window, since the server has to be stopped for the copy.
Stop MySQL and note where it is now
SELECT @@datadir;
Then stop the service cleanly so InnoDB flushes everything:
# CentOS 7
sudo systemctl stop mysqld
# Ubuntu 16.04
sudo systemctl stop mysql
sudo systemctl status mysqld # confirm "inactive (dead)"
Copy the data
Use rsync -a to preserve ownership, permissions and timestamps. Note the trailing slash on the source; without it you would end up with /data/mysql/mysql.
sudo mkdir -p /data/mysql
sudo rsync -av /var/lib/mysql/ /data/mysql/
sudo chown -R mysql:mysql /data/mysql
Don't delete the old directory yet. I always rename it so I can fall back quickly:
sudo mv /var/lib/mysql /var/lib/mysql.bak
Point the server at the new directory
On CentOS 7, edit /etc/my.cnf; on Ubuntu 16.04, edit /etc/mysql/mysql.conf.d/mysqld.cnf. Change or add these lines under [mysqld]:
[mysqld]
datadir=/data/mysql
socket=/data/mysql/mysql.sock
If you change the socket path, the client needs to know about it too. Add a [client] section in the same file:
[client]
socket=/data/mysql/mysql.sock
Honestly I prefer to leave the socket where it was (/var/lib/mysql/mysql.sock on CentOS, /var/run/mysqld/mysqld.sock on Ubuntu), because then no application config has to change. In that case keep the socket setting unchanged and only move datadir; create an empty /var/lib/mysql owned by mysql so the socket has somewhere to live.
SELinux on CentOS 7
With SELinux in enforcing mode, mysqld may only write to files labelled mysqld_db_t. Label the new path permanently and apply it:
sudo semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?"
sudo restorecon -Rv /data/mysql
If semanage is missing, install it with sudo yum install policycoreutils-python. Check the result with ls -Z /data/mysql; the files should show mysqld_db_t.
AppArmor on Ubuntu 16.04
AppArmor confines mysqld with the profile in /etc/apparmor.d/usr.sbin.mysqld. Rather than editing that file (it gets replaced on package upgrades), add an alias in /etc/apparmor.d/tunables/alias:
echo 'alias /var/lib/mysql/ -> /data/mysql/,' | sudo tee -a /etc/apparmor.d/tunables/alias
sudo systemctl restart apparmor
The alias tells AppArmor that every rule written for /var/lib/mysql/ also applies to /data/mysql/.
Start it and check
sudo systemctl start mysqld # or mysql on Ubuntu
sudo systemctl status mysqld
SELECT @@datadir, @@socket;
SHOW TABLES FROM mysql;
@@datadir must show /data/mysql/. Run a quick write to be sure the server can create files:
CREATE DATABASE dirtest; CREATE TABLE dirtest.t (id INT); INSERT INTO dirtest.t VALUES (1); DROP DATABASE dirtest;
I wait a few days before removing the old copy:
sudo rm -rf /var/lib/mysql.bak
If the service won't start
mysqld: Can't create/write to file '/data/mysql/ib_logfile0' (Errcode: 13 - Permission denied)in the error log on CentOS: SELinux is blocking access. Checksudo ausearch -m avc -ts recentand redo Step 4. Settingsetenforce 0confirms the diagnosis but is not a fix.- The same error on Ubuntu, with
apparmor="DENIED"lines in/var/log/syslog: the alias was not loaded. Runsudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqldand try again. ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock': the server started, but the client is looking at the old socket path. Add the[client]socket line or leave the socket where it was.- On Ubuntu,
mysqldstarts and immediately exits with[ERROR] Could not open file '/var/log/mysql/error.log': unrelated to the move, but it means the log directory was lost. Recreate it withmkdir -p /var/log/mysql && chown mysql:adm /var/log/mysql.
Check the audit log first. It's almost always the security framework.