This makes all the sense in the world to me.
InnoDB creates data pages and index pages that are 16K each. If rows of data are being inserted, updated, deleted, committed, and rolled back, you are going to have FRAGMENTATION !!!
There are two cases where you can have internal fragmentation:
- A single row could be written in multiple data pages because certain column values make a row too big to fit in the data page.
- Having a TEXT column with 32K of data in it.
In those two cases, a single row spanning multiple data pages would have to be chained like a linked list. The internally generated list of data pages would always have to be navigated when the row is read.
Giving credit where credit is due, PostgreSQL implemented a very brilliant mechanism called TOAST (The Oversized-Attribute Storage Technique) to keep oversized data outside of tables to stem the tide of this kind of internal fragmentation.
Have used mysqldump to make a file with CREATE TABLE statements, followed by lots of INSERTs, you get a fresh table with no unused space along with contiguous data and index pages when loading the mysqldump into a new server.
For my explanantions, let's assume you have an InnoDB table in the CUSTODIA database called userinfo
If you would like to compress a table, you have three(3) options
Option 1
OPTIMIZE TABLE CUSTODIA.userinfo;
Option 2
ALTER TABLE CUSTODIA.userinfo ENGINE=InnoDB;
Option 3
CREATE TABLE CUSTODIA.userinfo2 LIKE CUSTODIA.userinfo;
INSERT INTO CUSTODIA.userinfo2 SELECT * FROM CUSTODIA.userinfo;
DROP TABLE CUSTODIA.userinfo;
ALTER TABLE CUSTODIA.userinfo2 RENAME CUSTODIA.userinfo;
CAVEAT : Option 3 is no good on a table with constraints. Option 3 is perfect for MyISAM.
Now for your questions:
Question 1. Why is there this difference between original and restored database size?
As explained above
Question 2. Is it safe to assume that restored database is OK, although this difference in size?
If you want to make absolutely sure that the data on both servers are identical, simply run this command on both DB servers:
CHECKSUM TABLE CUSTODIA.userinfo;
Hopefully, the checksum value is identical for the same table on both servers. If you have dozens, even hundreds, of tables, you may have to script it.
Question 3 : How does MySQL calculate data_length? Is it an estimate?
You are using the correct method in summing up the data_length and index_length. Based on my explanation of fragmentation, it is an estimate.
Question 4. Can I safely reduce production's ibdata file size to 3.6GiB with no down-time?
GOOD NEWS !!! You absolutely can compress it !!! In fact, how would like to compress it to a fraction of that number ??? Follow these two links because I addressed this issue in StackOverflow and ServerFault.
https://stackoverflow.com/questions/3927690/howto-clean-a-mysql-innodb-storage-engine/4056261#4056261
https://serverfault.com/questions/230551/mysql-innodb-innodb-file-per-table-cons/231400#231400
BAD NEWS !!! Sorry, but you will have a 3-5 minute window of downtime for rebuilding ib_logfile0 and ib_logfile1 as well shrinking ibdata1 once and for all. It's well worth it since it will be a one-time operation.
mysqldump has the --where option to execute a WHERE clause for a given table.
Although it is not possible to mysqldump a join query, you can export specific rows from each table so that every row fetched from each table will be involved in the join later on.
For your given query, you would need to mysqldump three times:
First, mysqldump all table3 rows with name in ('fee','fi','fo','fum'):
mysqldump -u... -p... --where="name in ('fee','fi','fo','fum')" mydb table3 > table3.sql
Next, mysqldump all table2 rows that have matching table3_id values from the first mysqldump:
mysqldump -u... -p... --lock-all-tables --where="table3_id in (select id from table3 where name in ('fee','fi','fo','fum'))" mydb table2 > table2.sql
Then, mysqldump all table1 rows that have matching table1_id values from the second mysqldump:
mysqldump -u... -p... --lock-all-tables --where="id in (select table1_id from table2 where table3_id in (select id from table3 where name in ('fee','fi','fo','fum')))" mydb table1 > table1.sql
Note: Since the second and third mysqldumps require using more than one table, --lock-all-tables must be used.
Create your new database:
mysqladmin -u... -p... mysqladmin create newdb
Finally, load the three mysqldumps into another database and attempt the join there in the new database.
mysql -u... -p... -D newdb < table1.sql
mysql -u... -p... -D newdb < table2.sql
mysql -u... -p... -D newdb < table3.sql
In mysql client, run your join query
mysql> use newdb
mysql> select table1.id, table1.level, table2.name, table2.level
from table1 join table2 on table1.id = table2.table1_id
join table3 on table3.id = table2.table3_id
where table3.name in ('fee', 'fi', 'fo', 'fum');
Give it a Try !!!
WARNING : If not indexed correctly, the second and third mysqldumps may take forever !!!
Just in case, index the following columns:
ALTER TABLE table2 ADD INDEX (table1_id);
ALTER TABLE table2 ADD INDEX (table3_id);
ALTER TABLE table3 ADD INDEX (name,id);
I'll assume id is the primary key of table3.
Best Answer
I created a script so that you switch the old DBName with the new DBName
Here is the script
Here is the sample file (junk.txt, 30 lines)
Here is the output
Give it a Try !!!