So what does the DATA_FREE column tell us? It tells us the free data within InnoDB for that particular table. A table can share a tablespace with multiple other tables.
 
The tablespace which is used by a table depends on whether the innodb_file_per_table was enabled during table creation and/or at the last time the table was rebuild (e.g. by OPTIMIZE TABLE).
 
If innodb_file_per_table was always disabled then this query probably reports the correct free space:
SELECT DATA_FREE FROM INFORMATION_SCHEMA.TABLES WHERE ENGINE='InnoDB' LIMIT 1;
This is because all tables will share 1 tablespace.
 
If innodb_file_per_table was always enabled (new default for 5.6!) then this would report the free space:
SELECT SUM(DATA_FREE) FROM INFORMATION_SCHEMA.TABLES WHERE ENGINE='InnoDB';
This is because eache table will have it's own tablespace.
 
But how could we combine these two?
 
To give this a try I create a MySQL 5.6.10 sandbox with 4 tables of which only 2 have their own tablespace.
 
My first try is to use my udf_fileexists UDF:
 
mysql> SELECT TABLE_NAME,DATA_FREE, -> udf_fileexists(CONCAT(TABLE_SCHEMA,'/',TABLE_NAME,'.ibd')) AS ibd_file -> FROM information_schema.tables WHERE ENGINE='InnoDB'; +----------------------+-----------+----------+ | TABLE_NAME | DATA_FREE | ibd_file | +----------------------+-----------+----------+ | t1 | 4194304 | 1 | | t2 | 4194304 | 1 | | t3 | 66060288 | 0 | | t4 | 66060288 | 0 | | innodb_index_stats | 0 | 1 | | innodb_table_stats | 0 | 1 | | slave_master_info | 0 | 1 | | slave_relay_log_info | 0 | 1 | | slave_worker_info | 0 | 1 | +----------------------+-----------+----------+ 9 rows in set (0.02 sec) mysql> SELECT (SELECT DATA_FREE FROM INFORMATION_SCHEMA.TABLES WHERE -> NOT udf_fileexists(CONCAT(TABLE_SCHEMA,'/',TABLE_NAME,'.ibd')) -> AND ENGINE='InnoDB' LIMIT 1) + (SELECT SUM(DATA_FREE) -> FROM INFORMATION_SCHEMA.TABLES WHERE -> udf_fileexists(CONCAT(TABLE_SCHEMA,'/',TABLE_NAME,'.ibd')) -> AND ENGINE='InnoDB') AS TOTAL_DATA_FREE; +-----------------+ | TOTAL_DATA_FREE | +-----------------+ | 74448896 | +-----------------+ 1 row in set (0.03 sec)
 
So that works, but it requires loading a UDF. Luckily it's also possible to only use INFORMATION_SCHEMA by using the INNODB_SYS_TABLES table. This works only for 5.6 and newer.
 
mysql> SELECT T.TABLE_SCHEMA,T.TABLE_NAME,T.DATA_FREE,ST.SPACE,SD.PATH -> FROM INFORMATION_SCHEMA.TABLES T LEFT JOIN INFORMATION_SCHEMA.INNODB_SYS_TABLES ST -> ON ST.NAME=CONCAT(T.TABLE_SCHEMA,'/',T.TABLE_NAME) -> LEFT JOIN INFORMATION_SCHEMA.INNODB_SYS_DATAFILES SD ON SD.SPACE=ST.SPACE WHERE T.ENGINE='InnoDB'; +---------------------+----------------------+-----------+-------+----------------------------------+ | TABLE_SCHEMA | TABLE_NAME | DATA_FREE | SPACE | PATH | +---------------------+----------------------+-----------+-------+----------------------------------+ | mysql | innodb_table_stats | 0 | 1 | ./mysql/innodb_table_stats.ibd | | mysql | innodb_index_stats | 0 | 2 | ./mysql/innodb_index_stats.ibd | | mysql | slave_relay_log_info | 0 | 3 | ./mysql/slave_relay_log_info.ibd | | mysql | slave_master_info | 0 | 4 | ./mysql/slave_master_info.ibd | | mysql | slave_worker_info | 0 | 5 | ./mysql/slave_worker_info.ibd | | innodbfreespacetest | t1 | 4194304 | 6 | ./innodbfreespacetest/t1.ibd | | innodbfreespacetest | t2 | 4194304 | 7 | ./innodbfreespacetest/t2.ibd | | innodbfreespacetest | t3 | 66060288 | 0 | NULL | | innodbfreespacetest | t4 | 66060288 | 0 | NULL | +---------------------+----------------------+-----------+-------+----------------------------------+ 9 rows in set (0.02 sec) mysql> SELECT (SELECT SUM(DATA_FREE) FROM INFORMATION_SCHEMA.TABLES T -> LEFT JOIN INFORMATION_SCHEMA.INNODB_SYS_TABLES ST ON -> ST.NAME=CONCAT(T.TABLE_SCHEMA,'/',T.TABLE_NAME) WHERE SPACE<>0) + -> (SELECT DATA_FREE FROM INFORMATION_SCHEMA.TABLES T -> LEFT JOIN INFORMATION_SCHEMA.INNODB_SYS_TABLES ST -> ON ST.NAME=CONCAT(T.TABLE_SCHEMA,'/',T.TABLE_NAME) -> WHERE SPACE=0 LIMIT 1) AS INNODB_FREE_GLOBAL; +--------------------+ | INNODB_FREE_GLOBAL | +--------------------+ | 74448896 | +--------------------+ 1 row in set (0.04 sec)Calculating the free space can be useful if you don't have autoextend enabled on your datafiles (requires innodb_file_per_table to be disabled). It can also be useful to calculate how much space could be freed by running OPTIMIZE TABLE (requires innodb_file_per_table to be enabled).
Conclusion:
If you're on 5.6 you can use INFORMATION_SCHEMA and for 5.5 and earlier you have to use a UDF or have (a script) to peek in the MySQL datadir.
wow
ReplyDeleteGreat Article Daniel. Hope to read more soon !!
ReplyDeletecheers!
https://www.startechup.com
Good way to calculate free space .... Nice Article ....Good way to explain the whole process..
ReplyDeleteTarika
http://www.tarikatechnologies.com
Hello Daniela,
ReplyDeleteThanks for your informative clarification. At our Adoriasoft courses we are required to do the similar thing. Thanks to your post, now I know all the pecularities.