Search code examples
mysqldatabaseinnodb

Why does InnoDB give obviously false free space information


I tried to know how much extent ( "free space" ) does my database have after deleting a rather large table. ( Around 10GB )

I have run the command:

SELECT table_schema "Data Base Name", 
round( sum( data_free ) / 1024 / 1024 / 1024 ) "Free Space in GB" 
FROM information_schema.TABLES
GROUP BY table_schema;

which gave me a list of databases, and their "free spaces".

The problem is, that the database which had the 10GB table removed now has a 1500GB+ free space according to this report which is significally bigger than my actual hard drive capacity. ( which is around 200GB )

How is this possible? How could I get a more realistic report? Am I missing something?

UPDATE

As an experiment, I have added and removed an 1GB table in this database, now the report shows around 110GB more free space. Might there be a problem with my configuration, or is this a common issue?


Solution

  • (This is answering some of the questions buried in Comments.)

    Misnomer "Free" space only includes whole blocks, not spare room inside blocks, and many other details.

    Case 1: All tables are in ibdata1 -- SHOW TABLE STATUS (or the equivalent query into information_schema will show the same Data_free value, namely how much is free in ibdata1. This space can be reused by any table. It is hard to give the space back to the OS.

    Case 2: All tables are file_per_table -- Now each Data_free refers to the space for the table. And the SUM() is meaningful. (ibdata1 still exists, but it does not contain any real tables; there is a lot of other stuff that InnoDB needs.)

    Case 3: Mixture -- If you turn file_per_table on/off at various times, some tables will be in ibdata1, some will have their own tablespaces.

    Case 4: CREATE TABLESPACE in 5.7 -- For example, you can have a tablespace for each database.

    Case 5: PARTITIONed tables -- Each partition acts like a table.

    Case 6: 8.0 -- Even more changes are coming.

    Database == Directory In MySQL's directory tree each database can be seen as a filesystem directory. Within that directory can be seen some set of files for each table. The .frm file contains the table definition. If an .ibd file exists, the table was created with file_per_table. This may be the most reliable way to discover whether the table is file_per_table. (8.0 will have significant changes here.)

    How much space can I reuse? There is no good answer. Usually inserting a row will find space in the block where it belongs, and Data_free will not shrink. But, if there were block split(s), Data_free can drop by some multiple of 16KB (the block size) or 4MB (the "extent size" - or maybe it is 8MB?). Also, random inserts lead to BTree blocks being, on average, about 69% full.

    Changing innodb_file_per_table has no effect until the next CREATE TABLE or ALTER TABLE. And then it only has effect on where to put the newly created/copied data+indexes (ibdata1 or .ibd). It will not destroy data.

    Big tables usually have 4MB to 7MB of Data_free. When computing how many rows you can add, don't plan on Data_free dropping below that range.

    Avg_row_size should be useful. But sometimes it (and Rows) are poorly approximated. Their product (Data_length) is always correct. So, this might be a good estimate of "rows to go before grabbing more space from OS:

    (Data_free - 7M) / Avg_row_size
    

    Tablespace Recommendations: Put 'big' tables in file_per_table. Put 'tiny' tables in ibdata1 or database-specific tablespaces (5.7). Sorry, no simple recommendation on the dividing line between 'big' and 'tiny'. And it is clumsy to migrate a table: SET global innodb_file_per_table = ...;; logout; login (to pick up the global); ALTER TABLE tbl ENGINE=InnoDB;. And it is necessarily a full copy of the table.

    (Caveat: I have left out many details.)