Some time ago, Peter Zaitsev posted a blog titled “How well does your table fits in innodb buffer pool?” He used some special INFORMATION_SCHEMA tables developed for Percona Server 5.1 to report how much of each InnoDB table and index resides in your buffer pool.

As Peter pointed out, you can use this view into the buffer pool to watch a buffer pool warm up with pages as you run queries. You can also use it for capacity planning. If you expect some tables need to be fully loaded in the buffer pool to be used efficiently, but the buffer pool isn’t large enough to hold them, then it’s time to increase the size of the buffer pool.

The problem, however, was that system tables change from version to version. Specifically, the INNODB_BUFFER_POOL_PAGES_INDEX table no longer exists in Percona Server 5.6, and the INNODB_INDEX_STATS table changed some column names in Percona Server 5.5.8, and the table no longer exists in Percona Server 5.6. So many of the comments on Peter’s blog rightly pointed out that the example query didn’t work on subsequent versions of Percona Server. And MySQL Community Edition at the time didn’t have the feature at all. They asked for an update to the blog post.

So here’s an updated, simplified query to report the content of your buffer pool, tested on the most recent versions.

Percona Server 5.1 and 5.5:

USE information_schema; SET @page_size = @@innodb_page_size; SET @bp_pages = @@innodb_buffer_pool_size/@page_size; SELECT P.TABLE_NAME, P.PAGE_TYPE, CASE WHEN P.INDEX_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE '`SYS_%' THEN P.INDEX_NAME WHEN P.INDEX_NAME <> 'PRIMARY' THEN 'SECONDARY' ELSE 'PRIMARY' END AS INDEX_TYPE, COUNT(DISTINCT P.PAGE_NUMBER) AS PAGES, ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/@bp_pages,2) AS PCT_OF_BUFFER_POOL, CASE WHEN P.TABLE_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE 'SYS_%' THEN NULL ELSE ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/CASE P.INDEX_NAME WHEN 'PRIMARY' THEN TS.DATA_LENGTH/@page_size ELSE TS.INDEX_LENGTH/@page_size END, 2) END AS PCT_OF_INDEX FROM INNODB_BUFFER_PAGE AS P JOIN INNODB_SYS_TABLES AS T ON P.SPACE = T.SPACE JOIN TABLES AS TS ON (T.SCHEMA, T.NAME) = (TS.TABLE_SCHEMA, TS.TABLE_NAME) WHERE TS.TABLE_SCHEMA <> 'mysql' GROUP BY TABLE_NAME, PAGE_TYPE, INDEX_TYPE; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 USE information_schema ; SET @ page_size = @ @ innodb_page_size ; SET @ bp_pages = @ @ innodb_buffer_pool_size / @ page_size ; SELECT P .TABLE_NAME , P .PAGE_TYPE , CASE WHEN P .INDEX_NAME IS NULL THEN NULL WHEN P .TABLE_NAME LIKE '`SYS_%' THEN P .INDEX_NAME WHEN P .INDEX_NAME <> 'PRIMARY' THEN 'SECONDARY' ELSE 'PRIMARY' END AS INDEX_TYPE , COUNT ( DISTINCT P .PAGE_NUMBER ) AS PAGES , ROUND ( 100 * COUNT ( DISTINCT P .PAGE_NUMBER ) / @ bp_pages , 2 ) AS PCT_OF_BUFFER_POOL , CASE WHEN P .TABLE_NAME IS NULL THEN NULL WHEN P .TABLE_NAME LIKE 'SYS_%' THEN NULL ELSE ROUND ( 100 * COUNT ( DISTINCT P .PAGE_NUMBER ) / CASE P .INDEX_NAME WHEN 'PRIMARY' THEN TS .DATA_LENGTH / @ page_size ELSE TS .INDEX_LENGTH / @ page_size END , 2 ) END AS PCT_OF_INDEX FROM INNODB_BUFFER_PAGE AS P JOIN INNODB_SYS_TABLES AS T ON P .SPACE = T .SPACE JOIN TABLES AS TS ON ( T .SCHEMA , T .NAME ) = ( TS .TABLE_SCHEMA , TS .TABLE_NAME ) WHERE TS .TABLE_SCHEMA <> 'mysql' GROUP BY TABLE_NAME , PAGE_TYPE , INDEX_TYPE ;

MySQL 5.6 and 5.7 (this also works on Percona Server 5.6):

USE information_schema; SET @page_size = @@innodb_page_size; SET @bp_pages = @@innodb_buffer_pool_size/@page_size; SELECT P.TABLE_NAME, P.PAGE_TYPE, CASE WHEN P.INDEX_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE '`SYS_%' THEN P.INDEX_NAME WHEN P.INDEX_NAME <> 'PRIMARY' THEN 'SECONDARY' ELSE 'PRIMARY' END AS INDEX_TYPE, COUNT(DISTINCT P.PAGE_NUMBER) AS PAGES, ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/@bp_pages,2) AS PCT_OF_BUFFER_POOL, CASE WHEN P.TABLE_NAME IS NULL THEN NULL WHEN P.TABLE_NAME LIKE 'SYS_%' THEN NULL ELSE ROUND(100*COUNT(DISTINCT P.PAGE_NUMBER)/CASE P.INDEX_NAME WHEN 'PRIMARY' THEN TS.DATA_LENGTH/@page_size ELSE TS.INDEX_LENGTH/@page_size END, 2) END AS PCT_OF_INDEX FROM INNODB_BUFFER_PAGE AS P JOIN INNODB_SYS_TABLES AS T ON P.SPACE = T.SPACE JOIN TABLES AS TS ON T.NAME = CONCAT(TS.TABLE_SCHEMA, '/', TS.TABLE_NAME) WHERE TS.TABLE_SCHEMA <> 'mysql' GROUP BY TABLE_NAME, PAGE_TYPE, INDEX_TYPE; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 USE information_schema ; SET @ page_size = @ @ innodb_page_size ; SET @ bp_pages = @ @ innodb_buffer_pool_size / @ page_size ; SELECT P .TABLE_NAME , P .PAGE_TYPE , CASE WHEN P .INDEX_NAME IS NULL THEN NULL WHEN P .TABLE_NAME LIKE '`SYS_%' THEN P .INDEX_NAME WHEN P .INDEX_NAME <> 'PRIMARY' THEN 'SECONDARY' ELSE 'PRIMARY' END AS INDEX_TYPE , COUNT ( DISTINCT P .PAGE_NUMBER ) AS PAGES , ROUND ( 100 * COUNT ( DISTINCT P .PAGE_NUMBER ) / @ bp_pages , 2 ) AS PCT_OF_BUFFER_POOL , CASE WHEN P .TABLE_NAME IS NULL THEN NULL WHEN P .TABLE_NAME LIKE 'SYS_%' THEN NULL ELSE ROUND ( 100 * COUNT ( DISTINCT P .PAGE_NUMBER ) / CASE P .INDEX_NAME WHEN 'PRIMARY' THEN TS .DATA_LENGTH / @ page_size ELSE TS .INDEX_LENGTH / @ page_size END , 2 ) END AS PCT_OF_INDEX FROM INNODB_BUFFER_PAGE AS P JOIN INNODB_SYS_TABLES AS T ON P .SPACE = T .SPACE JOIN TABLES AS TS ON T .NAME = CONCAT ( TS .TABLE_SCHEMA , '/' , TS .TABLE_NAME ) WHERE TS .TABLE_SCHEMA <> 'mysql' GROUP BY TABLE_NAME , PAGE_TYPE , INDEX_TYPE ;

In both cases, the output looks something like the following (if I have read from a single table called test.foo):

+--------------+-------------------+------------+-------+--------------------+--------------+ | TABLE_NAME | PAGE_TYPE | INDEX_TYPE | PAGES | PCT_OF_BUFFER_POOL | PCT_OF_INDEX | +--------------+-------------------+------------+-------+--------------------+--------------+ | NULL | FILE_SPACE_HEADER | NULL | 1 | 0.00 | NULL | | NULL | IBUF_BITMAP | NULL | 1 | 0.00 | NULL | | NULL | INODE | NULL | 1 | 0.00 | NULL | | `test`.`foo` | INDEX | PRIMARY | 2176 | 3.32 | 98.37 | | `test`.`foo` | INDEX | SECONDARY | 2893 | 4.41 | 88.47 | +--------------+-------------------+------------+-------+--------------------+--------------+ 1 2 3 4 5 6 7 8 9 + -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- + -- -- -- - + -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- + | TABLE_NAME | PAGE_TYPE | INDEX_TYPE | PAGES | PCT_OF_BUFFER_POOL | PCT_OF_INDEX | + -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- + -- -- -- - + -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- + | NULL | FILE_SPACE_HEADER | NULL | 1 | 0.00 | NULL | | NULL | IBUF_BITMAP | NULL | 1 | 0.00 | NULL | | NULL | INODE | NULL | 1 | 0.00 | NULL | | ` test ` . ` foo ` | INDEX | PRIMARY | 2176 | 3.32 | 98.37 | | ` test ` . ` foo ` | INDEX | SECONDARY | 2893 | 4.41 | 88.47 | + -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- + -- -- -- - + -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- +

Unfortunately, the INFORMATION_SCHEMA tables report total size of secondary indexes for a table, but not the size of each index individually. Therefore this query shows the percent of index only for the primary index (which is also the clustered index, i.e. the table itself), and then all other secondary indexes grouped together.

PERFORMANCE_SCHEMA solution

The PERFORMANCE_SCHEMA also includes some information about the contents of the buffer pool. The MySQL SYS Schema makes it easy to query this. But this view doesn’t calculate the percentage of each table in the buffer pool, nor the percentage of the buffer pool occupied by each table.

mysql> SELECT * FROM sys.innodb_buffer_stats_by_table; +---------------+----------------------+------------+------------+-------+--------------+-----------+-------------+ | object_schema | object_name | allocated | data | pages | pages_hashed | pages_old | rows_cached | +---------------+----------------------+------------+------------+-------+--------------+-----------+-------------+ | test | foo | 149.64 MiB | 106.19 MiB | 9577 | 9577 | 9577 | 1050490 | | InnoDB System | SYS_TABLES | 160.00 KiB | 91.24 KiB | 10 | 10 | 10 | 594 | | InnoDB System | SYS_INDEXES | 128.00 KiB | 93.59 KiB | 8 | 8 | 8 | 1345 | | InnoDB System | SYS_COLUMNS | 80.00 KiB | 47.13 KiB | 5 | 5 | 5 | 761 | | InnoDB System | SYS_DATAFILES | 48.00 KiB | 16.40 KiB | 3 | 3 | 3 | 246 | | InnoDB System | SYS_FIELDS | 48.00 KiB | 16.02 KiB | 3 | 3 | 3 | 377 | | InnoDB System | SYS_FOREIGN | 48.00 KiB | 0 bytes | 3 | 3 | 3 | 0 | | InnoDB System | SYS_TABLESPACES | 48.00 KiB | 15.83 KiB | 3 | 3 | 3 | 242 | | InnoDB System | SYS_FOREIGN_COLS | 16.00 KiB | 0 bytes | 1 | 1 | 1 | 0 | . . . 1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql > SELECT * FROM sys .innodb_buffer_stats_by_table ; + -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- - + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- -- -- -- - + | object_schema | object_name | allocated | data | pages | pages_hashed | pages_old | rows_cached | + -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- -- -- -- + -- -- -- - + -- -- -- -- -- -- -- + -- -- -- -- -- - + -- -- -- -- -- -- - + | test | foo | 149.64 MiB | 106.19 MiB | 9577 | 9577 | 9577 | 1050490 | | InnoDB System | SYS_TABLES | 160.00 KiB | 91.24 KiB | 10 | 10 | 10 | 594 | | InnoDB System | SYS_INDEXES | 128.00 KiB | 93.59 KiB | 8 | 8 | 8 | 1345 | | InnoDB System | SYS_COLUMNS | 80.00 KiB | 47.13 KiB | 5 | 5 | 5 | 761 | | InnoDB System | SYS_DATAFILES | 48.00 KiB | 16.40 KiB | 3 | 3 | 3 | 246 | | InnoDB System | SYS_FIELDS | 48.00 KiB | 16.02 KiB | 3 | 3 | 3 | 377 | | InnoDB System | SYS_FOREIGN | 48.00 KiB | 0 bytes | 3 | 3 | 3 | 0 | | InnoDB System | SYS_TABLESPACES | 48.00 KiB | 15.83 KiB | 3 | 3 | 3 | 242 | | InnoDB System | SYS_FOREIGN_COLS | 16.00 KiB | 0 bytes | 1 | 1 | 1 | 0 | . . .