Tuesday, May 17, 2016

How to Shrink MySQL ibdata1 Size

In MySQL, when you are using InnoDB, all the tables and indexes are stored under the MySQL system tablespace.
MySQL system tablespace is ibdata1, which is located under /var/lib/mysql
The single ibdata1 file contains all the tables and indexes in your MySQL database. So, if you have a big database, this file size will grow really big.
In this tutorial, we’ll explain how to rebuild your entire MySQL database, and break the big MySQL system tablespace file into small individual MySQL table files.

mysql ibdata1

1. Big MySQL (and MariaDB) System Tablespace

There is a major drawback with the default big MySQL system tablespace approach.
Take this scenario as an example: You’ve uploaded 100GB worth of data into multiple tables in your MySQL.
Now, the ibdata1 file size will be around 100GB+.
# cd /var/lib/mysql

# ls -lh ibdata1
-rw-r-----. 1 mysql mysql 101G Jan 21 21:10 ibdata1
After few days, you deleted around 50GB worth of data from all those tables. The ibdata1 file size will not be reduced to around 50GB+, it will still stay at around 100GB+.
In the above case, later when you add 10GB worth of data to the tables, the ibdata1 file size doesn’t grow to 110GB, and stays at 100GB. Because, the file still has the unused space inside from the above 50GB of deleted data.
The problem is, you can’t reclaim those unused space after you delete the 50GB of data from ibdata1 file. There is a way to do it, but is too complicated (explained below), and involves taking the MySQL database down.
So, how do we avoid storing all the tables and indexes in a single ibdata1 file; instead store in multiple table files individually?

2. Set the innodb_file_per_table parameter

For this, you should use innodb_file_per_table parameter inside your /etc/my.cnf file under the “mysqld” section as shown below:
# vi /etc/my.cnf
[mysqld]
innodb_file_per_table
Note: If you are using MySQL 5.6.6 (or MariaDB) and higher, the above is the default setting.
In this example, on CentOS 6, the default MySQL that you get when installing from yum repository is still 5.1.73 as shown below.
# mysql --version
mysql  Ver 14.14 Distrib 5.1.73, for redhat-linux-gnu (x86_64) using readline 5.1
So, in this case, we should set the innodb_file_per_table in the my.cnf file.
Anytime you make any changes to my.cnf, you should restart the MariaDB MySQL database.
service mysqld restart
Note: For some reason if you want to set this parameter while the database is running, and don’t want to shutdown MariaDB, you can do the following set global from the mysql prompt.
mysql> set global innodb_file_per_table=1;

3. New Tables (and index) as individual files

From now on, when you create a new MySQL table, you’ll get individual files.
In this example, I created a new table called employee and uploaded around 20GB worth of data into it.
mysql> use database

mysql> create table employee ...

mysql> insert into employee ..
Two things will happen here:
First, it will create a subdirectory with the database name “database” under the /var/lib/mysql directory
# ls -l /var/lib/mysql/
drwx------. 2 mysql mysql   266240 Jan  5 12:11 database
..
Second, under this database directory name (i.e under database directory), you’ll see individual file EMPLOYEE.IBD getting created. The
 size of this file will be the size of the data that you uploaded only 
to that table. In this case, since we uploaded 20GB worth of data into 
this table, the EMPLOYEE.IBD filesize is around 20GB as shown below.
# cd /var/lib/mysql/database/

# ls -lh 
-rw-r-----. 1 mysql mysql  21G Jan 21 21:17 employee.ibd
Note: If you are using MyISAM database, you’ll see individual .MYD, .FRM and .MYI files

4. Extract existing tables from ibdata1

Next, if you want to extract an existing table from ibdata1 to it’s own individual file, then you have to optimize the table.
Let us say you have a table called benefits under database. This benefits table was created before we set the innodb_file_per_table in the my.cnf.
So, the benefits table will still be under ibdata1 file. To move this out of ibdata1 into it’s own IBD file, we have to optimize the table as shown below.
mysql> use database

mysql> optimize table benefits;
This will create the following individual file for the benefits table.
# cd /var/lib/mysql/database/

# ls -lh 
-rw-r-----. 1 mysql mysql  21G Jan 21 21:17 benefits.ibd
In this example, keep in mind that the original ibdata1 file still did not shrink. It is still around 100GB.
# ls -lh /var/lib/mysql/ibdata1
-rw-r-----. 1 mysql mysql 101G Jan 21 21:10 ibdata1
Note: You can also do the following.
mysql> alter table benefits engine=InnoDB;

5. Shrink ibdata1 File Size

Keep in mind that the ibdata1 still remains the same 101G size, it didn’t reduce the size yet.
# ls -lh /var/lib/mysql/ibdata1
-rw-r-----. 1 mysql mysql 101G Jan 21 21:10 ibdata1
To shrink the ibdata1 file, you need to perform the following steps:

6. Backup the Database

First, temporarily stop mysql database, and take a cold backup of the whole database. In case something goes wrong, you can use this cold backup to restore.
mkdir /backup

cd /var/lib

cp -r mysql /backup
Second, take a mysqldump backup of all the database.
mysqldump -u root -ptmppassword --all-databases > /backup/all-database.sql

7. Drop all your database

Next, drop all your database one by one. To view all the database to be dropped, use “show databases”
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| database           |
| sales              |
| mysql              |
+--------------------+
In this example, we are dropping two databases (database and sales) that exist in the mysql.
# mysql -u root -ptmppassword
mysql> drop database database;

mysql> drop database sales;
Note: Do not drop information_schema and mysql database.

8. Delete ibdata and ib_logfile

Next, shutdown the MySQL database.
service mysqld stop
Next, remove the ibdata1 file and all the individual ib_logfile* files:
cd /var/lib/mysql/

rm ibdata1

rm ib_logfile0

rm ib_logfile1

9. Import all the Database

Note: At this point, you should already have the following in your my.cnf file.
[mysqld]
innodb_file_per_table
Start the MySQL database.
service mysqld start
Import all the database from the mysqldump backup that we took earlier.
mysql -u root -ptmppassword --all-databases < /backup/all-database.sql
At this stage, the ibdata1 file, which is the MySQL system tablespace will be created from scratch, and in our example, it will not be 100GB anymore.
Now ibdata1 will be only few MB in size. All the database tables will be stored as individual files under the corresponding database subdirectory under /var/lib/mysql/

No comments:

Post a Comment