How to shrink/purge ibdata1 file in MySQL
Asked Answered
Q

10

581

I am using MySQL in localhost as a "query tool" for performing statistics in R, that is, everytime I run a R script, I create a new database (A), create a new table (B), import the data into B, submit a query to get what I need, and then I drop B and drop A.

It's working fine for me, but I realize that the ibdata file size is increasing rapidly, I stored nothing in MySQL, but the ibdata1 file already exceeded 100 MB.

I am using more or less default MySQL setting for the setup, is there a way for I can automatically shrink/purge the ibdata1 file after a fixed period of time?

Quamash answered 11/8, 2010 at 7:20 Comment(1)
Further reference: ibdata1 grows exponentially when innodb_file_per_table is configuredDuval
M
808

That ibdata1 isn't shrinking is a particularly annoying feature of MySQL. The ibdata1 file can't actually be shrunk unless you delete all databases, remove the files and reload a dump.

But you can configure MySQL so that each table, including its indexes, is stored as a separate file. In that way ibdata1 will not grow as large. According to Bill Karwin's comment this is enabled by default as of version 5.6.6 of MySQL.

It was a while ago I did this. However, to setup your server to use separate files for each table you need to change my.cnf in order to enable this:

[mysqld]
innodb_file_per_table=1

https://dev.mysql.com/doc/refman/5.6/en/innodb-file-per-table-tablespaces.html

As you want to reclaim the space from ibdata1 you actually have to delete the file:

  1. Do a mysqldump of all databases, procedures, triggers etc except the mysql and performance_schema databases.
  2. Drop all databases except the above 2 databases(mysql and performance_schema)
  3. Stop mysql
  4. Delete ibdata1 and ib_log files
  5. Start mysql
  6. Restore from dump

When you start MySQL in step 5 the ibdata1 and ib_log files will be recreated.

Now you're fit to go. When you create a new database for analysis, the tables will be located in separate ibd* files, not in ibdata1. As you usually drop the database soon after, the ibd* files will be deleted.

http://dev.mysql.com/doc/refman/5.1/en/drop-database.html

You have probably seen this:
http://bugs.mysql.com/bug.php?id=1341

By using the command ALTER TABLE <tablename> ENGINE=innodb or OPTIMIZE TABLE <tablename> one can extract data and index pages from ibdata1 to separate files. However, ibdata1 will not shrink unless you do the steps above.

Regarding the information_schema, that is not necessary nor possible to drop. It is in fact just a bunch of read-only views, not tables. And there are no files associated with the them, not even a database directory. The informations_schema is using the memory db-engine and is dropped and regenerated upon stop/restart of mysqld. See https://dev.mysql.com/doc/refman/5.7/en/information-schema.html.

Mills answered 11/8, 2010 at 9:7 Comment(22)
There's no need for the "=1" according to dev.mysql.com/doc/refman/5.1/en/…Berate
@JordanMagnuson I tried that on a test server, it wouldn't let me (logged in as the mysql root/admin user) drop information_schema. So, no.Goldie
@JordanMagnuson Don't bother to drop information_schema. It is in fact just a bunch of read-only views, not tables. And there are no files associated with the them. There isn't even a directory for the database. The informations_schema is using the memory db-engine and is dropped and regenerated upon stop/restart of mysqld. See dev.mysql.com/doc/refman/5.5/en/information-schema.html. Regarding performance_schema I haven't used that schema myself.Mills
a little cross-link: #3928190Delfeena
I don't know if this is a recent thing but once the innodb_file_per_table option is enabled you can simply run "ALTER TABLE <tablename> ENGINE=InnoDB" (even if it's already InnoDB) and it will move the table in to its individual file. No need to drop databases and such.Peskoff
That can be handy but I do not think it will shrink the ibdata1 file. The command will move data and index pages to a separate file, but ibdata1 will still hold metadata and buffers and still claim as much space as before.Mills
+1 FWIW, MySQL 5.6 enables innodb_file_per_table by default.Rambutan
For whatever reason, I have the ibdata1 file (~1GB in size) AND I have the .ibd files that represent the tables in each of my database folders. I'm not sure why I have both, since I have the "innodb_file_per_table" option set. I assume that it wouldn't be creating the individual files if that setting wasn't set. So should the ibdata1 file still be there even with that option set?Antibes
Yes, ibdata1 is expected to be present along with the other files. The ibdata1 file will still hold metadata about tables, the undo log and buffers.Mills
MySQL just caused me serious problems. I edited your answer in the hope this doesn't happen to somebody else. i.stack.imgur.com/kNXoo.png I dumped all of my databases, except for mysql. Notice that error in the middle of it on performance_schema? Yeah, well, my BIG DUMP.SQL file only contains the databases after that. I didn't notice until after I'd dropped my db's... I just lost ~15 databases!Baptistery
See also What is the best way to reduce the size of ibdata in mysql? over at Database Administrators.Pavla
but if I optimize all tables (hence extracting the data from the ibdata file) - can I stop mysql, delete the ibdata, start mysql (which will rebuild the ibdata file)? Or will the tables be corrupt?Diller
I've run out of space in my server because of the ibdata1 file, so I cannot even dump the databases. Would it be the same to just move the files at /var/lib/mysql (except "mysql", "ibdata1", "ib_logfile0" and "ib_logfile1") and then follow the steps? See #2482991Bulbous
Like some said you don't need to delete anything. You can just run mysqlcheck -op database_name as I understand it that is basically just another way of running alter table mysql command. Where you do need to still delete things is if you want to get rid of that big ibdata1 file. If you don't drop the database and recreate as explained you will still have that big file sitting there taking up space needlessly.Viceregal
This didn't quite work for me. After backing up my tables, dropping them and deleting the files specified above, I was unable to bring the mysql server back up (I got: InnoDB: Error: space header page consists of zero bytes in data file ./ibdata1 among other things). Even uninstalling and purging the mysql packages would not get it to work. I had to uninstall and manually delete the /var/lib/mysql directory completely and reinstall mysql.Lynnelynnea
Backing up to a .sql dump and restoring is completely infeasible for a 200GB database. Do you have a better solution? Can't I convert my tables to MyISAM and back again?Seeder
Just to clarify: You need to put "innodb_file_per_table=1" into your file. "innodb_file_per_table" is not enough.Garish
Using Server version: 5.7.14 MySQL Community Server (GPL) on Windows 10 as part of WampServer. innodb_file_per_table is not set by default. I have only one big ibdata1 file. Perhaps the default setting is only for ver 5.6.x only.Tabulate
Thanks for this answer. Actually information_schema cannot be drop. So we have to ignore it.Wahlstrom
Regarding step 1, here's a decent and fast way to do it: https://mcmap.net/q/74229/-mysqldump-dump-multiple-databases-from-separate-mysql-accounts-to-one-file. Also, you can adopt the same principle when re-importing from dumpParallax
Quickly scripted this answer in Bash, see below: https://mcmap.net/q/73156/-how-to-shrink-purge-ibdata1-file-in-mysqlGablet
Deleted those files and Mysql 8.0.19 will not start with error [InnoDB] Plugin initialization aborted with error Generic error. [Server] Failed to initialize DD Storage Engine [Server] Data Dictionary initialization failed. [Server] Aborting Why would a database become so crappy that you cannot do simple attach database task. you can do this even in Microsoft SQL serverChondrule
G
60

Adding to John P's answer,

For a linux system, steps 1-6 can be accomplished with these commands:

  1. mysqldump -u [username] -p[root_password] [database_name] > dumpfilename.sql
  2. mysqladmin -u [username] -p[root_password] drop [database_name]
  3. sudo /etc/init.d/mysqld stop
  4. sudo rm /var/lib/mysql/ibdata1
    sudo rm /var/lib/mysql/ib_logfile*
  5. sudo /etc/init.d/mysqld start
  6. mysqladmin -u [username] -p[root_password] create [database_name]
  7. mysql -u [username] -p[root_password] [database_name] < dumpfilename.sql

Warning: these instructions will cause you to lose other databases if you have other databases on this mysql instance. Make sure that steps 1,2 and 6,7 are modified to cover all databases you wish to keep.

Gondi answered 31/10, 2014 at 7:11 Comment(9)
You need to repeat 1,2, and 6 for every database that has InnoDB tables.Lignocellulose
You need a couple more steps in between #5 and #6. You have to recreate the database and re-assign permissions. So from mysql client command promptcreate database database_name; and then grant all privileges on database_name.* to 'username'@'localhost' identified by 'password';Viceregal
@Viceregal I didn't need to grant privileges when doing this. Possibly because I recreated the database with the same name?Collenecollet
To type the password at a Password: prompt (which is a safer practice), just put -p without any actual password.Purine
Triggers, events and routines/functions are not dumped unless you tell mysqldump to do so. Add the --triggers, --events and --routines as well if any of your databases contain them. Also, just dump with --all-databases to dump all databases at once instead of one by one.Lowpitched
Now this causes the error InnoDB: File ./ibdata1: 'open' returned OS error 71. Cannot continue operation, so cannot restart the server!Benilda
Big "thanks" guys :/ your advice broke my server. Now it won't start with the same error as mentioned above ^ and no advices help. Now I'll have to reinstall mysql entirely to fix it.Winnick
@Winnick shouldn't be a problem since you obviously backed up your database or made a snapshot of the machine running it before running random commands you found online. Answers are supposed to be a guide, you shouldn't be running commands that you don't understand the implications of. To the point: you can Google this error as well, and you will find that it's most likely permissions related.Rojas
@Rojas it was all fine with permissions. I tried various approaches but nothing helped, so I ended up wasting another hour to restore broken DB. This answer is dangerousWinnick
A
34

When you delete innodb tables, MySQL does not free the space inside the ibdata file, that's why it keeps growing. These files hardly ever shrink.

How to shrink an existing ibdata file:

https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html#innodb-resize-system-tablespace

You can script this and schedule the script to run after a fixed period of time, but for the setup described above it seems that multiple tablespaces are an easier solution.

If you use the configuration option innodb_file_per_table, you create multiple tablespaces. That is, MySQL creates separate files for each table instead of one shared file. These separate files a stored in the directory of the database, and they are deleted when you delete this database. This should remove the need to shrink/purge ibdata files in your case.

More information about multiple tablespaces:

https://dev.mysql.com/doc/refman/5.6/en/innodb-file-per-table-tablespaces.html

Accusatory answered 11/8, 2010 at 8:31 Comment(1)
first link broken, closest match I could find: dev.mysql.com/doc/refman/5.5/en/…Diphtheria
G
18

Quickly scripted the accepted answer's procedure in bash:

#!/usr/bin/env bash
dbs=$(mysql -BNe 'show databases' | grep -vE '^mysql$|^(performance|information)_schema$')
mysqldump --events --triggers --databases $dbs > alldatabases.sql && \
    echo "$dbs" | while read -r db; do
        mysqladmin drop "$db"
    done && \
    mysql -e 'SET GLOBAL innodb_fast_shutdown = 0' && \
    /etc/init.d/mysql stop && \
    rm -f /var/lib/mysql/ib{data1,_logfile*} && \
    /etc/init.d/mysql start && \
    mysql < alldatabases.sql

Save as purge_binlogs.sh and run as root.

Excludes mysql, information_schema, performance_schema (and binlog directory).

Assumes you have administrator credendials in /root/.my.cnf and that your database lives in default /var/lib/mysql directory.

You can also purge binary logs after running this script to regain more disk space with:

PURGE BINARY LOGS BEFORE CURRENT_TIMESTAMP;
Gablet answered 26/7, 2018 at 16:55 Comment(1)
Still not sure why, but today some of my InnoDB tables were corrupted during a similar process, so I wouldn't remove alldatabases.sql before double checking if all tables are healthy. As for some improvements: set innodb_fast_shutdown=0 before shutdown, set autocommit=0 before importing SQL file, execute COMMIT and set autocommit=1 after importing SQL file, use mysqlcheck --all-databases before deleting backup.Manuel
H
14

If you use the InnoDB storage engine for (some of) your MySQL tables, you’ve probably already came across a problem with its default configuration. As you may have noticed in your MySQL’s data directory (in Debian/Ubuntu – /var/lib/mysql) lies a file called ‘ibdata1′. It holds almost all the InnoDB data (it’s not a transaction log) of the MySQL instance and could get quite big. By default this file has a initial size of 10Mb and it automatically extends. Unfortunately, by design InnoDB data files cannot be shrinked. That’s why DELETEs, TRUNCATEs, DROPs, etc. will not reclaim the space used by the file.

Homoio answered 24/4, 2012 at 6:32 Comment(1)
That link is deadDubonnet
B
11

What nobody seems to mention is the impact innodb_undo_log_truncate setting can have.

After reading Percona's blog post about the topic, I've enabled in my MariaDB 10.6 the truncation of UNDO LOG entries which filled 95% of ibdata1, and, after a complete drop and restore, from that moment on my ibdata1 never grew anymore.

With the default innodb_undo_log_truncate = 0 my ibdata1 easily reached 10% of databases space occupation, aka tens of Gigabytes.

With innodb_undo_log_truncate = 1, ibdata1 it's firm at 76 Mb.

Birch answered 5/5, 2022 at 5:58 Comment(4)
Unbelievable... All the upvoted wrong answers... Nobody seems to know about this innodb_undo_log_truncate option... I was searching for solutions for years, your answer is priceless! I'm running on tmpfs/inmemory, with your help InnoDB now eats up only 50MB, and this is constant!Inaptitude
Additional reading: dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.htmlInaptitude
"Truncating undo logs that reside in the system tablespace [ibdata1] is not supported." seems to contradict your claim @Imagyar. Ref dev.mysql.com/doc/refman/8.0/en/…Hermilahermina
When innodb_file_per_table is ON (default), what will modify the system tablespace? Schema changes? Undo log "administration"? Stats/metrics? Locks? I only guess these, so please really correct me! Are there any guidance on how to minimize these?Inaptitude
D
7

If your goal is to monitor MySQL free space and you can't stop MySQL to shrink your ibdata file, then get it through table status commands. Example:

MySQL > 5.1.24:

mysqlshow --status myInnodbDatabase myTable | awk '{print $20}'

MySQL < 5.1.24:

mysqlshow --status myInnodbDatabase myTable | awk '{print $35}'

Then compare this value to your ibdata file:

du -b ibdata1

Source: http://dev.mysql.com/doc/refman/5.1/en/show-table-status.html

Deepfry answered 26/1, 2012 at 15:3 Comment(0)
O
4

In a new version of mysql-server recipes above will crush "mysql" database. In old version it works. In new some tables switches to table type INNODB, and by doing so you will damage them. The easiest way is to:

  • dump all you databases
  • uninstall mysql-server,
  • add in remained my.cnf:
    [mysqld]
    innodb_file_per_table=1
  • erase all in /var/lib/mysql
  • install mysql-server
  • restore users and databases
Outcurve answered 7/8, 2017 at 13:38 Comment(0)
M
3

We recently experienced a bug in MariaDB 10.6.13 (fixed in 10.6.14) that caused our ibdata1 file to grow 5+GB/day. The file grew to over 400GB by the time alerts on disk space usage started to get sent out.

Our databases in total are about 865GB so a logical backup / restore with mysqldump would have taken days at least to do. If you have a large database you can use mydumper to do a logical dump/restore much more quickly since apparently that is still the only way to shrink the ibdata1 file.

Here are some notes / procedures I followed when I rebuilt our DB servers to get ibdata1 back down to a reasonable size:

  • I use innodb_file_per_table=1 already.

  • I used this tool to determine the space usage in ibdata1 was indeed undo log pages (it took hours to run, this was before I was aware that there was a bug in 10.6.13 and didn't know for sure what was causing ibdata1 to grow so much).

  • We have one primary and three replicas.

  • I first took one replica out of production, used mydumper to dump/restore it, then I used mariabackup to back up the rebuilt replica. I used that backup to restore the rest of the DB servers (which is much faster).

  • We already have a full backup routine using mariabackup. Before you do anything, make sure you have a known good full backup!

Rebuilding the first server

Stop all writes to the database.

I do flush tables; set global read_only = 1; to make 100% sure nothing is writing to the DB during backup (and stop replica; on replicas of course, read_only=1 is already set on replicas in our config).

Use mydumper to dump databases. I used this script which will send an email when it's done (if you've got email set up):

#!/usr/bin/bash
time mydumper -t "$(nproc)" \
  --compress \
  --triggers \
  --routines \
  --events \
  --rows 10000000 \
  --verbose 3 \
  --build-empty-files \
  --regex '^(?!(test|performance_schema|information_schema|sys))' \
  --logfile /mnt/mysql_backups/ibdatafix_backup.log \
  --outputdir /mnt/mysql_backups/ibdatafix_backup 2>&1

echo "DUMP ON $(hostname) DONE" | mail -s "DUMP ON $(hostname) DONE" root

This took 12 minutes to dump 865GB worth of DBs on a pretty beefy enterprise server with 4 x SSDs in hardware RAID 10. On the older server this takes less than one hour.

Grep the log for any errors.

I tried simply dropping our databases, shutting down MariaDB, and removing ibdata1 but that caused problems so I shut down MariaDB, wiped the entire data dir (rm -rf /var/lib/mysql/*), and ran mysql_install_db to start fresh (make sure you have good backups!).

Then I added this config to move the undo logs out of ibdata1. Adjust for your environment, of course, if you want to do this. At this point I think it's a good idea to do so mainly because there's no way to truncate ibdata1 and I don't want to have to rebuild all of our servers again any time soon.

# About 3GB.
innodb_max_undo_log_size = 3221225472

# I moved the undo logs into a separate volume. You can set this to
# /var/lib/mysql.
innodb_undo_directory = /var/lib/mysql_undo_logs

# This appears to only work if innodb_undo_directory is set and
# the undo logs are separate from ibdata1.
innodb_undo_log_truncate = 1

innodb_undo_tablespaces = 3

Add these to the config temporarily for the restore:

innodb_flush_log_at_trx_commit=0
net_read_timeout=60
performance_schema=off
innodb_adaptive_hash_index=off
sql_mode=NO_ENGINE_SUBSTITUTION
innodb_doublewrite=off
slow_query_log = 0

Now start up MySQL / MariaDB and restore:

#!/usr/bin/bash

time myloader \
  --compress-protocol \
  --directory /mnt/mysql_backups/ibdatafix_backup \
  --overwrite-tables \
  --queries-per-transaction 1000 \
  # The recommendation here is half of your available cores. I set this 
  # too high and it lead to timeouts and a botched restore.
  --threads 4 \
  --verbose 3 > restore.log 2>&1

echo "RESTORE ON $(hostname) DONE" | mail -s "RESTORE ON $(hostname) DONE" root

This took around 12 hours to complete.

Make sure to check restore.log for errors (grep -i error restore.log). My first restore was totally botched because of errors I didn't catch.

Remove the temp config and restart MySQL/MariaDB.

Before I did anything else, I ran pt-table-checksum to make sure the primary and all replicas had identical data (esp. the newly restored replica).

If you need to rebuild other servers (other replicas/the primary) you can use mariabackup (or Percona XtraBackup) which is much faster. Something like this (I ran this on a replica so I added all the slave options):

# On the freshly rebuilt server:
rm -rf /mnt/mysql_backups/ibdatafix_backup/*

mariabackup --defaults-file=/etc/mysql/my.cnf --parallel=4 \
  --compress --compress-threads=4 --safe-slave-backup --slave-info \
  --user=root --backup --target-dir=/mnt/mysql_backups/ibdatafix_backup

# Then on the other server(s), shut down the DB, wipe /var/lib/mysql, and copy the backup back over:
systemctl stop mariadb/mysql

cd /var/lib/mysql

rm -rf *

# This is an NFS share mounted on all DB servers.
rsync -av /mnt/mysql_backups/ibdatafix_backup .

mariabackup --decompress --parallel=8 --remove-original --target-dir=.

mariabackup --prepare --use-memory=32G --target-dir=.

chown -R mysql:mysql .

systemctl start mariadb/mysql

# Set up replication etc.

This all took about two hours per server to complete (865GB in databases, enterprise servers, 10gig network, 4 x SSD in RAID 10).

Undo log truncation is definitely working now. I had set innodb_max_undo_log_size to 10MB initially and MariaDB was constantly truncating them. It'll log when it does so.

ibdata1 is now holding steady on our highly active servers at just 12MB.

Malpighi answered 14/8, 2023 at 4:23 Comment(0)
D
0

As already noted you can't shrink ibdata1 (to do so you need to dump and rebuild), but there's also often no real need to.

Using autoextend (probably the most common size setting) ibdata1 preallocates storage, growing each time it is nearly full. That makes writes faster as space is already allocated.

When you delete data it doesn't shrink but the space inside the file is marked as unused. Now when you insert new data it'll reuse empty space in the file before growing the file any further.

So it'll only continue to grow if you're actually needing that data. Unless you actually need the space for another application there's probably no reason to shrink it.

Darlinedarling answered 21/11, 2011 at 17:57 Comment(5)
I think you're a little too dismissive of the need to free up the space.Lixiviate
I have a 60Gig Solid State partition. I run out of space fast, since I work with 4+gig databases. I'm looking to move mysql to another partition soon, but this question and it's answers will help me in the meantimeUngley
Thank you for this answer, it's very helpful. I have cleared out some tables from legacy data... it's good to know that the size on disk won't grow again any time soon.Holly
I have a 500G ibdata1 file - but almost all of the data that was stored in it is now stored in per-database files. I very much need to shrink this collossal waste of space!Powwow
Complete nonsense! A file that keeps bloating up needs to be trimmed whether you're running out of space or not. I would call it a storage leak.Purine

© 2022 - 2024 — McMap. All rights reserved.