You've missed calculating all of InnoDB's overhead for storing these rows. You should have:
4 (INT)
+ 4 (INT)
+ 1 (TINYINT)
+ 1 (TINYINT)
+ 4 (INT)
+ 4 (TIMESTAMP)
+ 1 (Null bitmap, rounded up to whole bytes)
+ 5 (Row header)
+ 6 (ROW_ID: Implicit cluster key, because you are missing a PRIMARY KEY)
+ 6 (TRX_ID: Transaction ID)
+ 7 (ROLL_PTR: Rollback/undo pointer)
= 43 bytes per row
Then you also need to account for page fill rates (pages aren't filled to 100% by design) which adds ~7% at an absolute minimum:
43
* 1 / (15/16)
= 45.86 bytes per row
Additionally you will have overhead in allocated but unused space.
So actually, getting ~47 bytes per row is not bad at all. The worst case would be for overhead to consume ~50% causing the table to take ~86 bytes per row.
For each of your secondary keys, note that their space consumption will look like (to use id_1
as an example):
4 (INT)
+ 1 (Null bitmap, rounded up to whole bytes)
+ 5 (Row header)
+ 6 (ROW_ID: Implicit cluster key)
= 16 bytes per row
* 1 / (15/16)
= 17.06 bytes per row
It may be useful to read the following posts about InnoDB data structures to learn more: