Moving individual MySQL tables on disk
Tags: sql, mysql, devops, database
Published: 2018-02-13 15:28:26

---
title: "Moving individual MySQL tables on disk"
published: true
tags: sql,mysql,devops,databases
canonical_url: https://www.moxio.com/blog/28/moving-individual-mysql-tables-on-disk
description: "Moving one single MySQL table to another location on disk (e.g. to free up disk space) turns out to be far from straightforward. In this post I will describe what we can learn of our failed approaches, and what solution we eventually came up with."
cover_image: https://cdn-images-1.medium.com/max/828/0*IDJHzccNPmcY6iFG.png
---

You may encounter the situation where you want to move one single MySQL database table to another (location on) disk, e.g. to free up disk space. It turns out that this process is far from straightforward. In this post I will describe several of our failed approaches (since failures are a great opportunity for learning), and the [solution](https://www.moxio.com/blog/28/moving-individual-mysql-tables-on-disk#solution) we eventually came up with.

### Why move tables on disk?

A while ago we received an alert that disk space on one of our servers at Moxio was gradually running low. We constantly monitor our servers for factors that could threaten normal operation, and receive a first alert when the free disk space drops below 30% of the total disk size. In that way we still have plenty of time to act upon it before the disk is actually&nbsp;full.

![Graph of free disk space before moving tables](https://cdn-images-1.medium.com/max/823/0*1K_X4cy158Bclsjb.png)<figcaption>Graph of free disk space before moving tables</figcaption>

Usually in such situations we could free up sufficient disk space by deleting old backups and temporary files, or moving certain file storage directories off the main hard drive to an external storage array. This time almost all disk space was taken by the MySQL database server however. Some tables had been growing steadily over time, eating up more and more disk space. Since this was data we had to keep, our only sustainable solution was to move these tables to another&nbsp;disk.

### The desired situation

Our ideal solution would be to move only the large individual tables to the external storage array. With that approach we could keep the other tables in the database on the (much faster) SSD disk, not sacrificing performance unnecessarily. So is this even possible with&nbsp;MySQL?

Historically, MySQL used to store all table data and indices in the [_system tablespace_](https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_system_tablespace), represented by one or more [ibdata files](https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_ibdata_file) on disk. This means that data from multiple databases and tables was stored in the same file, making it impossible to move one of them to another location. Then MySQL 4.1.1 introduced [file-per-table tablespaces](https://dev.mysql.com/doc/refman/5.7/en/innodb-multiple-tablespaces.html) for InnoDB with the [innodb\_file\_per\_table](https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_file_per_table) setting, which would store data and indices for newly created tables in a separate&nbsp;[.ibd file](https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_ibd_file) per table. This setting became enabled by default in MySQL&nbsp;5.6.6.

When using file-per-table tablespaces, it is possible to use the DATA DIRECTORY = 'path' clause with CREATE TABLE to [place the data for the table outside the main MySQL data directory](https://dev.mysql.com/doc/refman/5.7/en/tablespace-placing.html) as of MySQL 5.6. This means that a setup as desired would be technically possible.

### Failed approaches

In our case we were dealing with existing tables however. We tried several unsuccessful approaches to move these to another directory before eventually finding a satisfying solution.

#### Changing the data directory after&nbsp;creation

Since a DATA DIRECTORY can be specified to change a table’s storage location when creating the table, it seems logical that it would also be possible to change this location after creation by using this same option with ALTER TABLE. Indeed, ALTER TABLE [syntactically](https://dev.mysql.com/doc/refman/5.7/en/alter-table.html) supports DATA DIRECTORY as a table option. The documentation explicitly states however that this option [is ignored](https://dev.mysql.com/doc/refman/5.7/en/alter-table.html#alter-table-options) (except when partitioning). It thus seems impossible to change DATA DIRECTORY for an existing&nbsp;table.

#### Symlinking the data&nbsp;files

Another approach we came up with was manually moving the table’s data files to the other disk, symlinking them back to their original location inside MySQL’s main data directory. According to the documentation however, although MySQL supports symlinking [entire database directories](https://dev.mysql.com/doc/refman/5.7/en/symbolic-links-to-databases.html) or [individual MyISAM tables](https://dev.mysql.com/doc/refman/5.7/en/symbolic-links-to-tables.html), using symbolic links to InnoDB tables [is not supported](https://dev.mysql.com/doc/refman/5.7/en/symbolic-links-to-tables.html) and may cause strange problems.

#### Creating a copy in the desired&nbsp;location

Since DATA DIRECTORY can only be specified when creating a table, we also tried creating a copy of the table in the desired location, copying over all data from the old table to the new copy and renaming the new table to take the place of the old one. This would look somewhat&nbsp;like:

```sql
SHOW CREATE TABLE `table_name`;
```

This returns the CREATE TABLE statement that would create the table structure of table\_name. We now execute that exact statement, but substitute a new table name and append a DATA DIRECTORY clause:

```sql
CREATE TABLE `table_name_new` /* ... */ DATA DIRECTORY='/path/to/desired/location';
```

Now we have an empty copy of table\_name, in schema that is. We then copy all data over from the existing table to the new one and issue an [atomic rename](https://dev.mysql.com/doc/refman/5.7/en/rename-table.html) to swap the two&nbsp;tables:

```sql
INSERT INTO `table_name_new` SELECT * FROM `table_name`; RENAME TABLE `table_name` TO `table_name_old`, `table_name_new` TO `table_name`;
```

Here we ran into problems however. When running the INSERT INTO&nbsp;... SELECT query, MySQL failed with the error&nbsp;message

```
ERROR 1206 (HY000): The total number of locks exceeds the lock table size
```

It turns out that when using INSERT INTO&nbsp;... SELECT, MySQL needs to [set locks on the rows read](https://www.percona.com/blog/2006/07/12/insert-into-select-performance-with-innodb-tables/) from the source table to ensure proper replication. These locks take up space in the [InnoDB buffer pool](https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html). The size of this pool is configurable using [innodb\_buffer\_pool\_size](https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size), but set to 128 MB by default. Since the table we’re trying to move is quite large (more than 50 million rows), MySQL is bound to run out of its space for holding&nbsp;locks.

Even if we could sufficiently increase innodb\_buffer\_pool\_size to make this work, this approach is quite inefficient once you think about it. When inserting data into the new copy, MySQL has to serialize the data to disk and build indices for it, which takes a lot of time. We already have the serialized data and indices however: they’re right there for the original table! Instead of letting MySQL recreate the entire index and data file from scratch, we should look for a way to re-use the table data that is already&nbsp;there.

### The solution

Eventually we found a solution for smoothly moving tables on disk, based on a guide for copying tablespaces to another MySQL instance [in the MySQL manual](https://dev.mysql.com/doc/refman/5.7/en/tablespace-copying.html). The solution comes down to moving the existing tablespace on disk, dropping and re-creating the table with the desired DATA DIRECTORY, and then re-attaching the saved tablespace to the new&nbsp;table.

Step-by-step, this looks as follows. First we ensure that all data is flushed from MySQL’s caches and buffers to the tablespace on&nbsp;disk:

```sql
FLUSH TABLES `table_name` FOR EXPORT;
```

FLUSH TABLES also locks the table for the duration of the connection or until we unlock it. The lock ensures the data in the table cannot change while we are moving the files. To maintain these locks we should keep open the MySQL connection in which we ran FLUSH TABLES&nbsp;.... In a new terminal window we move the tablespace files to a temporary location. In this case we use our home directory:

```bash
$ mv /var/lib/mysql/database_name/table_name.{ibd,cfg} ~
```

Now returning to our open MySQL session, we can release the locks (the tablespace has been safely put away in a consistent state), drop and re-create the table in its desired location:

```sql
UNLOCK TABLES; SHOW CREATE TABLE `table_name`; DROP TABLE `table_name`; CREATE TABLE `table_name` /* ... */ DATA DIRECTORY='/path/to/desired/location';
```

This will also create fresh tablespace files on disk (in the desired location) for the newly created table. We do not want these (because we want to put back our old tablespace files), so we discard this new tablespace:

```sql
ALTER TABLE `table_name` DISCARD TABLESPACE;
```

At this moment we can copy the saved original tablespace files to the location where MySQL now expects them, making sure to preserve ownership and permissions:

```bash
$ cp -a ~/table_name.{ibd,cfg} /path/to/desired/location
```

The final step is to let MySQL import the original tablespace files back from&nbsp;disk:

```sql
ALTER TABLE `table_name` IMPORT TABLESPACE;
```

Except for copying the tablespace files to the other disk, this whole process is very fast, as MySQL just takes the original table data and indices and does not have to rebuild&nbsp;these.

### Recap

After several failed attempts (in which we did learn a lot about MySQL internals though), we eventually found a solution for efficiently moving tables to another location on disk. Using this approach we moved some of our largest and fastest-growing tables to an external storage array, freeing up the diskspace necessary to keep our server&nbsp;going.

![Graph of free disk space after moving tables](https://cdn-images-1.medium.com/max/828/0*IDJHzccNPmcY6iFG.png)<figcaption>Graph of free disk space after moving tables</figcaption>

Another thing to remember: if you know that a database table will become large, plan its location on disk in advance. When creating the table, specifying a custom location on disk (using DATA DIRECTORY) is simple and saves you the hassle of the process described in this post down the&nbsp;road.

---

##### Enjoyed reading this post?

My colleagues and I regularly write webdev-related blog posts like this to share the things we learn or discover. You can find all our posts on the [Moxio blog](https://www.moxio.com/blog). Get notified of new posts by following [me](https://twitter.com/arnoutboks) or [Moxio](https://twitter.com/moxio) on Twitter or by subscribing to our [RSS feed](https://www.moxio.com/rss/blog).