Archive

Posts Tagged ‘InnoDB’

Improving InnoDB Transaction Reporting

August 18th, 2010

Everybody knows that parsing the output of SHOW ENGINE INNODB STATUS is hard, especially when you want to track the information historically, or want to aggregate any of the more dynamic sections such as the TRANSACTIONS one.

Within the InnoDB plugin the INFORMATION_SCHEMA.INNODB_TRX table was added, which allowed you to at least get some of the information on each transaction, but not the full breadth of information that SHOW ENGINE INNODB STATUS provided.

“This is nice..” I thought “..but why not go the whole hog..?”.. And so I set about doing that, and opened up Bug#53336. In a very short time, I was in a review process with the InnoDB team, the patch was cleaned up, and (after a little hiccup) everything has been pushed for the next milestone.

Here’s the docs notes that I wrote for it today (with a little more annotation):

This patch adds the following columns:

mysql> DESC innodb_trx;
+----------------------------+---------------------+------+-----+---------------------+-------+
| FIELD                      | Type                | NULL | KEY | DEFAULT             | Extra |
+----------------------------+---------------------+------+-----+---------------------+-------+
...
| trx_operation_state        | varchar(64)         | YES  |     | NULL                |       |
| trx_tables_in_use          | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_tables_locked          | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_lock_structs           | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_lock_memory_bytes      | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_rows_locked            | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_rows_modified          | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_concurrency_tickets    | bigint(21) UNSIGNED | NO   |     | 0                   |       |
| trx_isolation_level        | varchar(16)         | NO   |     |                     |       |
| trx_unique_checks          | int(1)              | NO   |     | 0                   |       |
| trx_foreign_key_checks     | int(1)              | NO   |     | 0                   |       |
| trx_last_foreign_key_error | varchar(256)        | YES  |     | NULL                |       |
| trx_adaptive_hash_latched  | int(1)              | NO   |     | 0                   |       |
| trx_adaptive_hash_timeout  | bigint(21) UNSIGNED | NO   |     | 0                   |       |
+----------------------------+---------------------+------+-----+---------------------+-------+
22 rows IN SET (0.05 sec)

Most of these are self explanatory, a lot of them duplicate the information within SHOW ENGINE INNODB STATUS (so you now no longer need to parse the output of that to get complete transaction information).

Below are some of my notes. For comparison, here’s an example of a transaction from the SHOW ENGINE INNODB STATUS statement:

---TRANSACTION 517, ACTIVE 1 sec, OS thread id 2958520320 inserting
mysql TABLES IN USE 2, locked 2
189 LOCK struct(s), heap size 27968, 54389 row LOCK(s), undo log entries 2406
MySQL thread id 2, query id 36 localhost root Sending DATA
INSERT IGNORE INTO t1 (SELECT (i * rand())*100, 1 FROM t1)

trx_operation_state - corresponds to “inserting”, InnoDB’s internal transaction state
trx_tables_in_use - corresponds to “mysql tables in use 2″, the number of table locks requested by MySQL via external_lock()
trx_tables_locked - corresponds to “locked 2″, the number of actual table locks taken via external_lock()
trx_lock_structs - corresponds to “189 lock struct(s)”, the size of the lock struct list
trx_lock_memory_bytes - corresponds to “heap size 27968″, the number of bytes allocated to locks structs
trx_rows_locked - corresponds to “54389 row lock(s)”, an estimation of the number of rows locked (delete marked rows may make it imprecise)
trx_rows_modified - corresponds to “undo log entries 2406″, the number of rows modified in the transaction (inserted, updated, deleted)

Not seen in the transaction output above:

trx_concurrency_tickets - corresponds to “thread declared inside InnoDB 89″ for a transaction in SHOW ENGINE INNODB STATUS, the number of concurrency tickets remaining for the transaction when innodb_thread_concurrency != 0
trx_isolation_level - the transactions isolation level
trx_unique_checks - whether the transaction has “SET UNIQUE_CHECKS = 0″
trx_foreign_key_checks - whether the transaction has “SET FOREIGN_KEY_CHECKS = 0″
trx_last_foreign_key_error - if the last statement in the transaction resulted in an FK error, the error text is printed here
trx_adaptive_hash_latched - corresponds to “holds adaptive hash latch” being printed for a transaction in SHOW ENGINE INNODB STATUS
trx_adaptive_hash_timeout - when innodb_adapative_hash_index is enabled (default), statements that try to get the adapative hash latch spin 10000 (BTR_SEA_TIMEOUT) times, re-trying getting the adaptive hash latch, before giving up. lower numbers here for a lot of transactions may indicate contention on the adaptive hash latch

In summary - no more having to parse SHOW ENGINE INNODB STATUS output for transaction information, now you can just do it with SQL (with all it’s aggregation goodness as well if you want to)!

MySQL , , ,

InnoDB Monitor Docs Extended

May 14th, 2009

After I blogged about the InnoDB Table and Tablespace monitors, the docs guys got in touch with me, and asked if I’d mind the information being integrated in to the official documentation. “Of course not!” I said.

So Paul DuBois picked up the task, and set about integrating what I had in the docs, as well as sending the InnoDB developers and myself some extra questions for more detail, and adding that in too. 

Paul is finished with this for now - and now there are two new sections within the “SHOW ENGINE INNODB STATUS and the InnoDB Monitors” page of the manual:

Thanks for the work Paul!

MySQL , ,

innodb_stats_on_metadata / innodb_adaptive_hash_index

May 13th, 2009

It occurred to me at the MySQL UC that I hadn’t blogged about either innodb_stats_on_metadata or innodb_adaptive_hash_index - Morgan Tocker convinced me to do a lightning talk at the end of the Percona conference about them - so I thought I’d do a quick write up on them here as well.
 
First - what’s the point in them? Being the guy that wrote the originating patches for both of them, I have a little background:

innodb_stats_on_metadata

InnoDB is fairly light weight when it gathers table and index statistics. In the default compiled in InnoDB for most users it will do 8 random(ish) dives in to the index and samples the page to try to ascertain the cardinality (selectivity) of the index for use by the optimizer. This being the case, not only is this done when you run ANALYZE TABLE on a table, it is also done during various other points:

The first is easy to digest.

The second and third both occur for each row change within InnoDB. row_update_statistics_if_needed() is called from row_insert_for_mysql()row_update_cascade_for_mysql(), and row_update_for_mysql().

The last in the list is interesting. In order to get an accurate(ish) count of the rows in the table, or the cardinality results etc. for the metadata commands, InnoDB runs the same procedure as an ANALYZE TABLE for all of the tables, because it doesn’t maintain accurate statistics in a table or index header somewhere like MyISAM (being MVCC getting an accurate row count is far more difficult for example).

This is great, if you run these commands periodically to check on their output yourself. Once you start trying to read these tables very often in a monitoring solution of any kind though, you will likely hit two distinct problems:

  1. God Damn that INFORMATION_SCHEMA access is SLLLOOOWWWWW
  2. If you are monitoring your queries, and their EXPLAIN plans, you may start to see “plan instability” - the optimizer randomly choosing different access paths for the queries

Behind door #1 there’s a couple of issues - we traverse the directories and table files in the  data directory for a lot of the information used in the output, as well as the ANALYZE that is done for InnoDB. We’ve done work in Worklog #3732 to optimize certain accesses to these INFORMATION_SCHEMA tables, but this didn’t solve the statistics issue.

The second issue is a little more subtle. You may have benchmarked your application statements in Dev and QA environments - but data sets change when they are in production. If table and index statistics are constantly being recalculated, there is a high probability that the optimizer will in fact choose different access paths for certain queries and tables where the rows that need to be scanned could be satisfied by more than one index.

Unless you are closely monitoring individual statements for their run time, access paths (EXPLAIN) etc. this is far more difficult to catch (often you will be seeing the “good” EXPLAIN plan a lot, and the “bad” one only surfaces every now and then too - so interactive debugging is made harder as well).

So, at the request of a pretty large SaaS customer, who was having both problems, innodb_stats_on_metadata was born - originally in Bug #26598. If you see any of the situations above, give it a try, and see how it works for you, as well.

Originally it was created as a non-dynamic variable, you could only set it on the command line or in an option file. It was altered by the InnoDB guys to be dynamic, and settable by SUPER privileged users. It’s documented in the InnoDB plugin documentation under the “Changes for Flexibility, Ease of Use and Reliability” chapter.

I’m on the fence about allowing this dynamically, as it still allows tools to change the setting as well (which often have the SUPER privilege to get at things like SHOW INNODB STATUS), when the DBA may have wanted this explicitly not allowed for all users.

Be good citizens tool vendors, and respect this setting please.

innodb_adaptive_hash_index

InnoDB has a really unique feature in the database world called “Adaptive Hash Indexes“. It monitors which rows are being accessed via index searches, and dynamically decides to cache those that are used the most often, by taking the BTREE index for the table the rows are from and converting it to a HASH index in memory, for quick row lookups.  Historically, this has performed really well (and still does today in most workloads).

Prior to the fix for the infamous Bug #15815 (the true start of our multi-core scalability jaunt, imho), it just hummed along with most people not noticing it around. Once we had the fix though, we started to see mutex contention in other places, in certain cases leading to InnoDB getting locked up, and stopping the server due to long semaphore waits - this happened within the code for adaptive hash indexes as well, and was reported as Bug #20358.

This has hit a pretty large number of users - many Support customers as well, and has had many engineer hours in trying to reproduce it reliably (without result, the bug is still “Open”, not “Verified”). As this started to hit a number of customers, I wrote a pretty simple patch for innodb_adaptive_hash_index to allow you to switch off the use of adaptive hash indexes altogether within InnoDB.

Now, for some this comes as a performance trade off - if you hit the above bug, it does allow you to work around it, but your application may benefit from Adaptive Hash Indexes too. If you favor reliability over performance, then use it - it fixes the problem (in a somewhat hackish way).

Yasufumi Kinoshita did a lot of great work, in Bug #29560 and Bug #26442, and there have been other issues found and fixed by the InnoDB team - Bug #39483 (closed at the time of writing) - and Mark over at Google - Bug #32149 (still verified at the time of writing). We hardly ever see Bug#20358 any more though, unless it’s on older versions.

This variable also gets a good mention in the InnoDB plugin documentation, under the ”Changes for Flexibility, Ease of Use and Reliability” again, and in it’s own section “Controlling Adaptive Hash Indexing“, because the variable has a nice side effect as well - it can also help to scale, specifically because of the kind of mutex contention that also lead to Bug #20358. 

The InnoDB manual mentions that you can monitor the contention via the SEMAPHORES section of SHOW INNODB STATUS by looking for “many threads waiting on an RW-latch created in btr0sea.c” - quite correctly.

You can also check out how much memory the hash index is using, and how well it’s being utilized, in the INSERT BUFFER AND ADAPTIVE HASH INDEX section:

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 5, seg size 7,
16104 inserts, 16104 merged recs, 15379 merges
Hash table size 2212699, used cells 846306, node heap has 1574 buffer(s)
0.03 hash searches/s, 0.63 non-hash searches

The last two rows apply to the Adaptive Hash index. “Hash Table size” and “used cells” are the number of cells (array slots) assigned to the hash table (always a prime number), and the number of those which are used, respectively. “used cells” was removed as part of Bug #36941 because of it having perform issues by scanning the hash table to find the used cells - in 5.1.28 - so you may not see this on your server. 

“node heap has 1574 buffer(s)” is where you can find out how much memory the hash index is using - the number here is the number of pages that are allocated to the hash index (and a page is 16K in InnoDB by default). 

The last row gives the number of searches both in the adaptive hash index, as well as those not satisfied by it - which can give you a hit ratio for hash read efficiency. 

I’ve made a few graphs for MEM to track these, so they can be monitored over time to get a better picture of adaptive hash index usage, to give you a better tool to judge for yourself what the impact may be for disabling it (see below). I’ll push these up today, and after some testing on a number of other servers, you should hopefully see them in a new advisor update soon.

InnoDB Adaptive Hash Index graphs for MEM

InnoDB Adaptive Hash Index graphs for MEM

MySQL 5.1 ,

InnoDB Table and Tablespace Monitors

January 19th, 2009

InnoDB allows you to dump information on its internal data dictionary out to stderr, by creating specific InnoDB monitor tables. Specifically the innodb_table_monitor and innodb_tablespace_monitor tables. This is covered to a limited extent in the MySQL Reference Manual at http://dev.mysql.com/doc/refman/5.0/en/innodb-monitor.html, however not to any great detail.

In the following I’ll use output created via these tables based upon the sample “sakila” database, and give details on how to attempt to correlate the information between the different outputs.

The output of SHOW INNODB STATUS is the output of the InnoDB Monitor, which is different than both the InnoDB Tablespace Monitor and the Innodb Table Monitor. You can also cause this information to be output to the MySQL error log every 15 seconds by creating an InnoDB table named innodb_monitor, like this:

CREATE TABLE innodb_monitor (a INT) ENGINE=InnoDB;

In order to invoke InnoDB to output it’s data dictionary information on the tablespace, you must create an InnoDB table, like this:

CREATE TABLE innodb_tablespace_monitor (a INT) ENGINE = InnoDB;

InnoDB will then dump the tablespace information to either the error log, or the console (if started without log-error).

Note: This only works fully when not using the innodb_file_per_table option, as it only dumps information on the shared tablespace, and not individual “tablespaces”.

================================================
060403 14:20:31 INNODB TABLESPACE MONITOR OUTPUT
================================================
FILE SPACE INFO: id 0
size 1152, free limit 832, free extents 3
not full frag extents 4: used pages 202, full frag extents 2
first seg id not used 0 114
SEGMENT id 0 86 space 0; page 209; res 28 used 28; full ext 0
fragm pages 28; free extents 0; not full extents 0: pages 0
SEGMENT id 0 87 space 0; page 209; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
SEGMENT id 0 88 space 0; page 209; res 16 used 16; full ext 0
fragm pages 16; free extents 0; not full extents 0: pages 0
SEGMENT id 0 89 space 0; page 209; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
SEGMENT id 0 90 space 0; page 209; res 16 used 16; full ext 0
fragm pages 16; free extents 0; not full extents 0: pages 0
SEGMENT id 0 91 space 0; page 209; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
–snip–
SEGMENT id 0 81 space 0; page 2; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
SEGMENT id 0 82 space 0; page 2; res 16 used 16; full ext 0
fragm pages 16; free extents 0; not full extents 0: pages 0
SEGMENT id 0 83 space 0; page 2; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
SEGMENT id 0 84 space 0; page 2; res 96 used 53; full ext 0
fragm pages 32; free extents 0; not full extents 1: pages 21
SEGMENT id 0 85 space 0; page 2; res 1 used 1; full ext 0
fragm pages 1; free extents 0; not full extents 0: pages 0
NUMBER of file segments: 108
Validating tablespace
Validation ok
—————————————
END OF INNODB TABLESPACE MONITOR OUTPUT
=======================================

Now lets break down what this means for each segment. Right near the end we have SEGMENT id 0 84:

SEGMENT id 0 84 space 0; page 2; res 96 used 53; full ext 0
fragm pages 32; free extents 0; not full extents 1: pages 21

Space 0, page 2 means that the inode of the segment is on page 2 of space 0. The system tablespace (ibdata files) always have the id 0.

res means how many pages we have allocated for the segment.

used means how many of these pages are actually used by the segment at the moment.

So for this segment we have 96 pages allocated, but only 53 are being used currently, leaving 43 pages unused.

full ext means that 0 extents are completely used.

fragm pages means that additionally we have used all of the first 32 pages that InnoDB first allocates for the segment.

free extents means the number of those extents that are allocated for the segment, but where none of the pages is used.

not full extents tells you the number of those extents that are allocated for the segment, where some of the pages are used but not all.

pages tells you how many pages are used within those extents.

An extent in InnoDB is always 64 pages at a size of 16kb - 1 MB.

The tablespace is divided into these 64 page extents. When a segment grows it starts at a single page, and InnoDB allocates the first 32 pages for it individually. After that, InnoDB allocates complete 64 page extents.

This is shown with the number of pages reserved - 32 initial pages + 64 pages for the next extent.

InnoDB bases it’s internal tablespace management as a filesystem. The analogy between an OS file system and an InnoDB tablespace is:

File system              <-> InnoDB
----------------------------------------------
disk partition           <-> tablespace
file                     <-> segment
inode                    <-> fsp0fsp.c 'inode'
fs space allocation unit <-> extent
disk block               <-> page (16 kB)

So currently, the table in question (SEGMENT id 0 84) is 96 pages in size, or 1.5Mb, and has 43 pages free, or 688Kb.

Now that we have this information, how do we try to find out which table this is? Let’s move on to the innodb_table_monitor output.

To direct the output of the InnoDB Table Monitor to the erro log create the following table:

create table innodb_table_monitor (a int) engine = innodb;

This produces the following output for the Sakila sample database:

 

===========================================
060403 14:29:35 INNODB TABLE MONITOR OUTPUT
===========================================
--------------------------------------
TABLE: name SYS_FOREIGN, id 0 11, columns 8, indexes 3, appr.rows 22
  COLUMNS: ID: DATA_VARCHAR prtype 1 len 0 prec 0; FOR_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; REF_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; N_COLS: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name ID_IND, id 0 11, fields 1/6, type 3
   root page 46, appr.key vals 22, leaf pages 1, size pages 1
   FIELDS:  ID DB_TRX_ID DB_ROLL_PTR FOR_NAME REF_NAME N_COLS
  INDEX: name FOR_IND, id 0 12, fields 1/2, type 0
   root page 47, appr.key vals 11, leaf pages 1, size pages 1
   FIELDS:  FOR_NAME ID
  INDEX: name REF_IND, id 0 13, fields 1/2, type 0
   root page 48, appr.key vals 12, leaf pages 1, size pages 1
   FIELDS:  REF_NAME ID
--------------------------------------
TABLE: name SYS_FOREIGN_COLS, id 0 12, columns 8, indexes 1, appr.rows 22
  COLUMNS: ID: DATA_VARCHAR prtype 1 len 0 prec 0; POS: DATA_INT len 4 prec 0; FOR_COL_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; REF_COL_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name ID_IND, id 0 14, fields 2/6, type 3
   root page 49, appr.key vals 22, leaf pages 1, size pages 1
   FIELDS:  ID POS DB_TRX_ID DB_ROLL_PTR FOR_COL_NAME REF_COL_NAME
--------------------------------------
TABLE: name sakila/actor, id 0 14, columns 8, indexes 2, appr.rows 200
  COLUMNS: actor_id: DATA_INT len 2 prec 0; first_name: type 12 len 135 prec 0; last_name: type 12 len 135 prec 0; last_update: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name PRIMARY, id 0 16, fields 1/6, type 3
   root page 51, appr.key vals 200, leaf pages 1, size pages 1
   FIELDS:  actor_id DB_TRX_ID DB_ROLL_PTR first_name last_name last_update
  INDEX: name idx_actor_last_name, id 0 17, fields 1/2, type 0
   root page 52, appr.key vals 121, leaf pages 1, size pages 1
   FIELDS:  last_name actor_id
  FOREIGN KEY CONSTRAINT sakila/fk_film_actor_actor: sakila/film_actor ( actor_id )
             REFERENCES sakila/actor ( actor_id )
--------------------------------------
--snip--
--------------------------------------
TABLE: name sakila/rental, id 0 26, columns 11, indexes 5, appr.rows 16305
  COLUMNS: rental_id: DATA_INT len 4 prec 0; rental_date: DATA_INT len 8 prec 0; inventory_id: DATA_INT len 3 prec 0; customer_id: DATA_INT len 2 prec 0; return_date: DATA_INT len 8 prec 0; staff_id: DATA_INT len 1 prec 0; last_update: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name PRIMARY, id 0 44, fields 1/9, type 3
   root page 207, appr.key vals 16305, leaf pages 53, size pages 97
   FIELDS:  rental_id DB_TRX_ID DB_ROLL_PTR rental_date inventory_id customer_id return_date staff_id last_update
  INDEX: name rental_date, id 0 45, fields 3/4, type 2
   root page 208, appr.key vals 17655, leaf pages 28, size pages 29
   FIELDS:  rental_date inventory_id customer_id rental_id
  INDEX: name idx_fk_inventory_id, id 0 46, fields 1/2, type 0
   root page 210, appr.key vals 4467, leaf pages 16, size pages 17
   FIELDS:  inventory_id rental_id
  INDEX: name idx_fk_customer_id, id 0 47, fields 1/2, type 0
   root page 211, appr.key vals 589, leaf pages 16, size pages 17
   FIELDS:  customer_id rental_id
  INDEX: name idx_fk_staff_id, id 0 48, fields 1/2, type 0
   root page 212, appr.key vals 1, leaf pages 13, size pages 14
   FIELDS:  staff_id rental_id
  FOREIGN KEY CONSTRAINT sakila/fk_rental_staff: sakila/rental ( staff_id )
             REFERENCES sakila/staff ( staff_id )
  FOREIGN KEY CONSTRAINT sakila/fk_rental_inventory: sakila/rental ( inventory_id )
             REFERENCES sakila/inventory ( inventory_id )
  FOREIGN KEY CONSTRAINT sakila/fk_rental_customer: sakila/rental ( customer_id )
             REFERENCES sakila/customer ( customer_id )
  FOREIGN KEY CONSTRAINT sakila/fk_payment_rental: sakila/payment ( rental_id )
             REFERENCES sakila/rental ( rental_id )
--------------------------------------
TABLE: name sakila/staff, id 0 27, columns 15, indexes 3, appr.rows 1
  COLUMNS: staff_id: DATA_INT len 1 prec 0; first_name: type 12 len 135 prec 0; last_name: type 12 len 135 prec 0; address_id: DATA_INT len 2 prec 0; picture: type 5 len 10 prec 0; email: type 12 len 150 prec 0; store_id: DATA_INT len 1 prec 0; active: DATA_INT len 1 prec 0; username: type 12 len 48 prec 0; password: type 12 len 120 prec 0; last_update: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name PRIMARY, id 0 49, fields 1/13, type 3
   root page 213, appr.key vals 1, leaf pages 3, size pages 4
   FIELDS:  staff_id DB_TRX_ID DB_ROLL_PTR first_name last_name address_id picture email store_id active username password last_update
  INDEX: name idx_fk_store_id, id 0 50, fields 1/2, type 0
   root page 214, appr.key vals 2, leaf pages 1, size pages 1
   FIELDS:  store_id staff_id
  INDEX: name idx_fk_address_id, id 0 51, fields 1/2, type 0
   root page 215, appr.key vals 2, leaf pages 1, size pages 1
   FIELDS:  address_id staff_id
  FOREIGN KEY CONSTRAINT sakila/fk_staff_store: sakila/staff ( store_id )
             REFERENCES sakila/store ( store_id )
  FOREIGN KEY CONSTRAINT sakila/fk_staff_address: sakila/staff ( address_id )
             REFERENCES sakila/address ( address_id )
  FOREIGN KEY CONSTRAINT sakila/fk_payment_staff: sakila/payment ( staff_id )
             REFERENCES sakila/staff ( staff_id )
  FOREIGN KEY CONSTRAINT sakila/fk_rental_staff: sakila/rental ( staff_id )
             REFERENCES sakila/staff ( staff_id )
  FOREIGN KEY CONSTRAINT sakila/fk_store_staff: sakila/store ( manager_staff_id )
             REFERENCES sakila/staff ( staff_id )
--------------------------------------
TABLE: name sakila/store, id 0 28, columns 8, indexes 3, appr.rows 2
  COLUMNS: store_id: DATA_INT len 1 prec 0; manager_staff_id: DATA_INT len 1 prec 0; address_id: DATA_INT len 2 prec 0; last_update: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name PRIMARY, id 0 52, fields 1/6, type 3
   root page 216, appr.key vals 2, leaf pages 1, size pages 1
   FIELDS:  store_id DB_TRX_ID DB_ROLL_PTR manager_staff_id address_id last_update
  INDEX: name idx_unique_manager, id 0 53, fields 1/2, type 2
   root page 217, appr.key vals 2, leaf pages 1, size pages 1
   FIELDS:  manager_staff_id store_id
  INDEX: name idx_fk_address_id, id 0 54, fields 1/2, type 0
   root page 218, appr.key vals 2, leaf pages 1, size pages 1
   FIELDS:  address_id store_id
  FOREIGN KEY CONSTRAINT sakila/fk_store_staff: sakila/store ( manager_staff_id )
             REFERENCES sakila/staff ( staff_id )
  FOREIGN KEY CONSTRAINT sakila/fk_store_address: sakila/store ( address_id )
             REFERENCES sakila/address ( address_id )
  FOREIGN KEY CONSTRAINT sakila/fk_customer_store: sakila/customer ( store_id )
             REFERENCES sakila/store ( store_id )
  FOREIGN KEY CONSTRAINT sakila/fk_inventory_store: sakila/inventory ( store_id )
             REFERENCES sakila/store ( store_id )
  FOREIGN KEY CONSTRAINT sakila/fk_staff_store: sakila/staff ( store_id )
             REFERENCES sakila/store ( store_id )
--------------------------------------
TABLE: name test/t3, id 0 13, columns 6, indexes 1, appr.rows 1
  COLUMNS: i: DATA_INT len 4 prec 0; j: DATA_VARCHAR prtype 1 len 4000 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name GEN_CLUST_INDEX, id 0 15, fields 0/5, type 1
   root page 50, appr.key vals 1, leaf pages 1, size pages 1
   FIELDS:  DB_ROW_ID DB_TRX_ID DB_ROLL_PTR i j
-----------------------------------
END OF INNODB TABLE MONITOR OUTPUT
==================================

InnoDB uses clustered indexes, so the actual row data is stored along with the PRIMARY KEY (be this user, or synthetically generated if no PRIMARY KEY was specified). We are looking for a PRIMARY KEY in the above output that is around 96 pages long, and that has 53 pages used.

First, here is the actual table that we are looking for, sakila.rental:

TABLE: name sakila/rental, id 0 26, columns 11, indexes 5, appr.rows 16305
  COLUMNS: rental_id: DATA_INT len 4 prec 0; rental_date: DATA_INT len 8 prec 0; inventory_id: DATA_INT len 3 prec 0; customer_id: DATA_INT len 2 prec 0; return_date: DATA_INT len 8 prec 0; staff_id: DATA_INT len 1 prec 0; last_update: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name PRIMARY, id 0 44, fields 1/9, type 3
   root page 207, appr.key vals 16305, leaf pages 53, size pages 97
   FIELDS:  rental_id DB_TRX_ID DB_ROLL_PTR rental_date inventory_id customer_id return_date staff_id last_update
  INDEX: name rental_date, id 0 45, fields 3/4, type 2
   root page 208, appr.key vals 17655, leaf pages 28, size pages 29
   FIELDS:  rental_date inventory_id customer_id rental_id
  INDEX: name idx_fk_inventory_id, id 0 46, fields 1/2, type 0
   root page 210, appr.key vals 4467, leaf pages 16, size pages 17
   FIELDS:  inventory_id rental_id
  INDEX: name idx_fk_customer_id, id 0 47, fields 1/2, type 0
   root page 211, appr.key vals 589, leaf pages 16, size pages 17
   FIELDS:  customer_id rental_id
  INDEX: name idx_fk_staff_id, id 0 48, fields 1/2, type 0
   root page 212, appr.key vals 1, leaf pages 13, size pages 14
   FIELDS:  staff_id rental_id
  FOREIGN KEY CONSTRAINT sakila/fk_rental_staff: sakila/rental ( staff_id )
             REFERENCES sakila/staff ( staff_id )
  FOREIGN KEY CONSTRAINT sakila/fk_rental_inventory: sakila/rental ( inventory_id )
             REFERENCES sakila/inventory ( inventory_id )
  FOREIGN KEY CONSTRAINT sakila/fk_rental_customer: sakila/rental ( customer_id )
             REFERENCES sakila/customer ( customer_id )
  FOREIGN KEY CONSTRAINT sakila/fk_payment_rental: sakila/payment ( rental_id )
             REFERENCES sakila/rental ( rental_id )

Among the wealth of obvious information here, for INDEX: name PRIMARY you can see:

 INDEX: name PRIMARY, id 0 44, fields 1/9, type 3
  root page 207, appr.key vals 16305, leaf pages 53, size pages 97
  FIELDS:  rental_id DB_TRX_ID DB_ROLL_PTR rental_date inventory_id customer_id return_date staff_id last_update

leaf pages is the number of used pages used by the clustered index

size pages is the overall number of pages allocated to the clustered index

Note: It is more difficult to correlate the relations between small tables (1 page allocated each for example). It would be nice if InnoDB were to expose unique IDs between the table and tablespace monitors. 

This output also gives information on the internal data dictionary tables used by InnoDB, which are not exposed anywhere else:

--------------------------------------
TABLE: name SYS_FOREIGN, id 0 11, columns 8, indexes 3, appr.rows 22
  COLUMNS: ID: DATA_VARCHAR prtype 1 len 0 prec 0; FOR_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; REF_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; N_COLS: DATA_INT len 4 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name ID_IND, id 0 11, fields 1/6, type 3
   root page 46, appr.key vals 22, leaf pages 1, size pages 1
   FIELDS:  ID DB_TRX_ID DB_ROLL_PTR FOR_NAME REF_NAME N_COLS
  INDEX: name FOR_IND, id 0 12, fields 1/2, type 0
   root page 47, appr.key vals 11, leaf pages 1, size pages 1
   FIELDS:  FOR_NAME ID
  INDEX: name REF_IND, id 0 13, fields 1/2, type 0
   root page 48, appr.key vals 12, leaf pages 1, size pages 1
   FIELDS:  REF_NAME ID
--------------------------------------
TABLE: name SYS_FOREIGN_COLS, id 0 12, columns 8, indexes 1, appr.rows 22
  COLUMNS: ID: DATA_VARCHAR prtype 1 len 0 prec 0; POS: DATA_INT len 4 prec 0; FOR_COL_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; REF_COL_NAME: DATA_VARCHAR prtype 1 len 0 prec 0; DB_ROW_ID: DATA_SYS prtype 8 len 6 prec 0; DB_TRX_ID: DATA_SYS prtype 8 len 6 prec 0; DB_ROLL_PTR: DATA_SYS prtype 8 len 7 prec 0;
  INDEX: name ID_IND, id 0 14, fields 2/6, type 3
   root page 49, appr.key vals 22, leaf pages 1, size pages 1
   FIELDS:  ID POS DB_TRX_ID DB_ROLL_PTR FOR_COL_NAME REF_COL_NAME
--------------------------------------

These are the tables that InnoDB uses to track FOREIGN KEY constraints internally.

Wow, with that lengthy post, I hope this gives people a little more insight in to how to find out whether your tables and tablespace are fragmented within InnoDB!

MySQL , ,