020:InnoDB Buffer Pool

Posted 米豆小书童

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了020:InnoDB Buffer Pool相关的知识,希望对你有一定的参考价值。

一. 缓冲池(Buffer Pool)

1.1 缓冲池介绍

  • 每次读写数据都是通过 Buffer Pool

  • Buffer Pool 中没有用户所需要的数据时,才去硬盘中获取;

  • 通过 innodb_buffer_pool_size进行设置总容量,该值设置的越大越好;

  • innodb_buffer_pool_instances 设置为多个缓冲池;

    • 总容量还是innodb_buffer_pool_size
    • 设置多个instance 可将热点打散,提高并发性能(建议设置成CPU个数值)
  • Buffer Pool也是以 页(page)为单位的,且大小和innodb_page_size一致;

1.2 Buffer Pool 性能测试

  • 18G的测试数据,80M条记录;

  • Buffer Pool使用的内存超过数据库的大小时,比如20G(库中所有数据都在内存中),此时的性能有了很大的提升;

  • 该图测试的是TPS (每秒事物数),sysbench中一个事物由18条SQL语句组成,即这里的QPS为4.5W

  • 内存减少 10% ,性能下降 60%

1.3 Buffer Pool的管理

1.3.1 Buffer Pool 的组成

1.Free List

  • Buffer Pool 刚启动时,有一个个16K的空白的页,这些页就存放(链表串联)在 Free List

2.LRU List

  • 当读取一个数据页的时候,就从 Free List 中取出一个页,存入数据,并将该页放入到 LRU List

3.Flush List

  • LRU List 中的页 第一次 被修改了,就将该页的 指针(page number) 放入了 Flush List (只要修改过,就放入,不管修改几次)
  • Flush List 中包含脏页(数据经过修改,但是未刷入磁盘的页)
  • Flush list 中存放的不是一个页,而是页的指针(page number)

1.3.2 查看Buffer Pool的状态

  1. 使用命令 show engine innodb status\\G
mysql gcdb@localhost:(none)> show engine innodb status;
+--------+------+----------------------------------------------------------------------------------+
| Type   | Name | Status                                                                           |
+--------+------+----------------------------------------------------------------------------------+
| InnoDB |      |                                                                                  |
|        |      | =====================================                                            |
|        |      | 2018-01-05 14:18:27 0x7fa8b4649700 INNODB MONITOR OUTPUT                         |
|        |      | =====================================                                            |

---------------省略其他输出-----------------

|        |      | ----------------------                                                           |
|        |      | BUFFER POOL AND MEMORY                                                           |
|        |      | ----------------------                                                           |
|        |      | Total large memory allocated 10994319360                                         |
|        |      | Dictionary memory allocated 14685357                                             |
|        |      | Buffer pool size   655280                                                        |
|        |      | Free buffers       648346                                                        |
|        |      | Database pages     6904                                                          |
|        |      | Old database pages 2662                                                          |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |
|        |      | Pending writes: LRU 0, flush list 0, single page 0                               |
|        |      | Pages made young 2, not young 0                                                  |
|        |      | 0.00 youngs/s, 0.00 non-youngs/s                                                 |
|        |      | Pages read 6663, created 241, written 988                                        |
|        |      | 0.00 reads/s, 0.00 creates/s, 0.39 writes/s                                      |
|        |      | Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000        |
|        |      | Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s |
|        |      | LRU len: 6904, unzip_LRU len: 0                                                  |
|        |      | I/O sum[0]:cur[0], unzip sum[0]:cur[0]                                           |

--在输出的结果中可以定位到BUFFER POOL AND MEMORY ,以下部分显示了缓冲地的状态以及占用内存的情况:

* Total large memory allocated 10994319360
lnnoDB 所分配的内存总量为10994319360 字节。

* Dictionary memory allocated 14685357
数据字典内存区使用 14685357字节

* Buffer pool size 16384
缓冲地中页的数量,这里是 655280 个页,总共占用10G 内存( 16K*655280) 。

* Free buffers 648346
缓冲地中free 链表中空闲页的数量。

* Database pages 41
Buffer Pool中使用了多少页(LRU List)

* Old database pages 2662
最近不被访问的数据

* Modified db pages
赃页的页数

* Pending reads 0
正在读取的页的数量,这里为0 。

* Pending writes: LRU 0, flush list 0, single page 0
正在进行刷新页的数量,刷新可以分为LRU 、flush list 、single page 三种类型,这在后面的

* Pages made young 2, not young 0
 0.00 youngs/s, 0.00 non-youngs/s
 -- young表示old-->new的状态

*  Pages read 6663, created 241, written 988
缓冲地中已经读取、创建和刷新页的次数,这里分别为6663 、241 、998 。

*  0.00 reads/s, 0.00 creates/s, 0.39 writes/s
过去一段时间内,每秒页的读取、也IJ 建和刷新的次数。注意: SHOW INNODB STATUS 输出
的是过去一段时间内的结果。

* Buffer pool hit rate 1000 / 1000
缓冲地的命中率,这是监控最为关注的一个性能指标。命中率越高,数据库的性能越好,这里为1 00% ,表示缓冲地能够缓存所有InnoDB 存储引擎表。

---------------省略其他输出-----------------

|        |      | ----------------------------                                                     |
|        |      | END OF INNODB MONITOR OUTPUT                                                     |
|        |      | ============================                                                     |
|        |      |                                                                                  |
+--------+------+----------------------------------------------------------------------------------+
(END)
  1. 使用元数据表 information_schema.INNODB_BUFFER_POOL_STATS
mysql gcdb@localhost:(none)> select * from information_schema.INNODB_BUFFER_POOL_STATS\\G
POOL_ID                          | 0
POOL_SIZE                        | 81910
FREE_BUFFERS                     | 80996
DATABASE_PAGES                   | 910
OLD_DATABASE_PAGES               | 339
MODIFIED_DATABASE_PAGES          | 0
PENDING_DECOMPRESS               | 0
PENDING_READS                    | 0
PENDING_FLUSH_LRU                | 0
PENDING_FLUSH_LIST               | 0
PAGES_MADE_YOUNG                 | 0
PAGES_NOT_MADE_YOUNG             | 0
PAGES_MADE_YOUNG_RATE            | 0.0
PAGES_MADE_NOT_YOUNG_RATE        | 0.0
NUMBER_PAGES_READ                | 865
NUMBER_PAGES_CREATED             | 45
NUMBER_PAGES_WRITTEN             | 104
PAGES_READ_RATE                  | 0.0
PAGES_CREATE_RATE                | 0.0
PAGES_WRITTEN_RATE               | 0.0
NUMBER_PAGES_GET                 | 69856
HIT_RATE                         | 0
YOUNG_MAKE_PER_THOUSAND_GETS     | 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS | 0
NUMBER_PAGES_READ_AHEAD          | 768
NUMBER_READ_AHEAD_EVICTED        | 0
READ_AHEAD_RATE                  | 0.0
READ_AHEAD_EVICTED_RATE          | 0.0
LRU_IO_TOTAL                     | 0
LRU_IO_CURRENT                   | 0
UNCOMPRESS_TOTAL                 | 0
UNCOMPRESS_CURRENT               | 0
POOL_ID                          | 1
POOL_SIZE                        | 81910
FREE_BUFFERS                     | 80843
DATABASE_PAGES                   | 1063
OLD_DATABASE_PAGES               | 400
MODIFIED_DATABASE_PAGES          | 0
PENDING_DECOMPRESS               | 0
PENDING_READS                    | 0

-- ---------------省略其他输出-----------------

mysql gcdb@localhost:(none)> select * from information_schema.INNODB_BUFFER_PAGE_LRU limit 1\\G
POOL_ID             | 0
LRU_POSITION        | 0
SPACE               | 0                    -- space id 表空间号
PAGE_NUMBER         | 7                    -- 对应的页号
PAGE_TYPE           | SYSTEM
FLUSH_TYPE          | 1
FIX_COUNT           | 0
IS_HASHED           | NO
NEWEST_MODIFICATION | 32993864040        -- 该页最近一次(最新)被修改的LSN值
OLDEST_MODIFICATION | 0                    -- 该页在Buffer Pool中第一次被修改的LSN值,FLushList是根据该值进行排序的
                                        -- 该值越小,表示该页应该最先被刷新
ACCESS_TIME         | 2688054668
TABLE_NAME          | <null>
INDEX_NAME          | <null>
NUMBER_RECORDS      | 0
DATA_SIZE           | 0
COMPRESSED_SIZE     | 0
COMPRESSED          | NO
IO_FIX              | IO_NONE
IS_OLD              | YES
FREE_PAGE_CLOCK     | 0
1 row in set
Time: 0.143s
mysql gcdb@localhost:(none)>

-----------------省略其他输出-----------------

1.3.3 Buffer Pool 在线调整

  • 从 MySQL 5.7 开始,可以在线修改 innodb_buffer_pool_size
mysql gcdb@localhost:(none)> show variables like "%innodb_buffer_pool_size%";
+-------------------------+-------------+
| Variable_name           | Value       |
+-------------------------+-------------+
| innodb_buffer_pool_size | 10737418240 |    -- innodb_buffer_pool_size为10G
+-------------------------+-------------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:(none)> set global innodb_buffer_pool_size=16*1024*1024*1024;    --调整为innodb_buffer_pool_size为16G
Query OK, 0 rows affected
Time: 0.008s
mysql gcdb@localhost:(none)> show variables like "%innodb_buffer_pool_size%";
+-------------------------+-------------+
| Variable_name           | Value       |
+-------------------------+-------------+
| innodb_buffer_pool_size | 17179869184 |   --调整为innodb_buffer_pool_size为16G
+-------------------------+-------------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:(none)> show engine innodb status;
+--------+------+----------------------------------------------------------------------------------+
| Type   | Name | Status                                                                           |
+--------+------+----------------------------------------------------------------------------------+
| InnoDB |      |                                                                                  |
|        |      | =====================================                                            |
|        |      | 2018-01-05 15:23:16 0x7fa8b4649700 INNODB MONITOR OUTPUT                         |
|        |      | =====================================                                            |
--    ---------------省略其他输出-----------------
|        |      | ----------------------                                                           |
|        |      | BUFFER POOL AND MEMORY                                                           |
|        |      | ----------------------                                                           |
|        |      | Total large memory allocated 17590910976                                         | --lnnoDB 所分配的内存总量为17590910976字节
|        |      | Dictionary memory allocated 14685357                                             |
|        |      | Buffer pool size   1048496                                                       |
|        |      | Free buffers       1041477                                                       |
|        |      | Database pages     7019                                                          |
|        |      | Old database pages 2662                                                          |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |
|        |      | Pending writes: LRU 0, flush list 0, single page 0                               |
|        |      | Pages made young 2, not young 0                                                  |
|        |      | 0.00 youngs/s, 0.00 non-youngs/s                                                 |
|        |      | Pages read 6663, created 356, written 1158                                       |
|        |      | 0.00 reads/s, 0.00 creates/s, 0.00 writes/s                                      |
|        |      | No buffer pool page gets since the last printout                                 |
|        |      | Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s |
|        |      | LRU len: 7019, unzip_LRU len: 0                                                  |
|        |      | I/O sum[0]:cur[0], unzip sum[0]:cur[0]                                           |
|        |      | ----------------------                                                           |
|        |      | INDIVIDUAL BUFFER POOL INFO                                                      |
|        |      | ----------------------                                                           |
|        |      | ---BUFFER POOL 0                                                                 |
|        |      | Buffer pool size   131062                                                        | --BUFFER POOL 0 原先分配 81910页变为131062
|        |      | Free buffers       130152                                                        |
|        |      | Database pages     910                                                           |
|        |      | Old database pages 339                                                           |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |
|        |      | Pending writes: LRU 0, flush list 0, single page 0                               |
|        |      | Pages made young 0, not young 0                                                  |
|        |      | 0.00 youngs/s, 0.00 non-youngs/s                                                 |
|        |      | Pages read 865, created 45, written 104                                          |
|        |      | 0.00 reads/s, 0.00 creates/s, 0.00 writes/s                                      |
|        |      | No buffer pool page gets since the last printout                                 |
|        |      | Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s |
|        |      | LRU len: 910, unzip_LRU len: 0                                                   |
|        |      | I/O sum[0]:cur[0], unzip sum[0]:cur[0]                                           |
|        |      | ---BUFFER POOL 1                                                                 |
|        |      | Buffer pool size   131062                                                        |
|        |      | Free buffers       129999                                                        |
|        |      | Database pages     1063                                                          |
|        |      | Old database pages 400                                                           |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |


mysql gcdb@localhost:(none)> set global innodb_buffer_pool_size=8*1024*1024*1024;   -- 缩小,没修改的页被丢弃,修改的需要刷回磁盘
Query OK, 0 rows affected
Time: 0.001s

mysql gcdb@localhost:(none)> show variables like "%innodb_buffer_pool_size%";
+-------------------------+------------+
| Variable_name           | Value      |
+-------------------------+------------+
| innodb_buffer_pool_size | 8589934592 |
+-------------------------+------------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:(none)>
  • MySQL 5.7之前的版本,修改innodb_buffer_pool_size,需要重启

1.3.4 LRU List 的管理

1.3.4.1使用mid point 的LRU算法

  • LRU是Least Recently Used的缩写,即最近最久未使用,常用于页面置换算法,是为虚拟页式存储管理服务的。
  • 当该页被第一次读取时,将该页先放在 mid point的位置(因为无法保证一定是活跃);
  • 取当被读到第二次 时,才将改页放入到 new page 的首部;
  • innodb_old_blocks_pct 参数控制 mid point 的位置,默认是 37 ,即 3/8 的位置

LRU 中new page和old page是在一个链表上的,想像成排队,访问多的就从mid point排到了链表的前面然后后冷的页就慢慢被挤到了old page中,如果old中的数据继续被多次访问,还是会回到new page中。

  • 过程如下:
    • 1 : mid --> new
    • 2 : mid --> old --> new
    • 3 : mid --> old --> 刷回磁盘
    • 4 : new --> old --> 刷回磁盘
  • 当Free List中没有空余的页时,就需要从 old page 中最后的页(被淘汰的页)给取出,给新的查询所使用
  • 如果被淘汰的页是脏页(page number在Flush List中),则需要先刷回磁盘后,再给新的查询使用
mysql gcdb@localhost:(none)> show variables like "%innodb_old_blocks_pct%"
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |   --调整为innodb_buffer_pool_size为8G
+-----------------------+-------+
1 row in set
Time: 0.012s
  • 避免扫描语句污染LRU

    • 当使用 select * from tablename; 时,该语句会读取某个页很多次(即该页可能被读取了两次以上,读取一条记录,就需要读一次页 )

1.3.4.2 innodb_old_blocks_time

mysql gcdb@localhost:(none)> show variables like"%innodb_old_blocks_time%"
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |   --设置为1s
+------------------------+-------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:(none)>
  1. 当该页被第一次 读取时,将该页放在mid point 位置,但是随后无论你读多少次 ,我在这 innodb_old_blocks_time 的时间内都不管(都视作只读取了一次 ),等这个时间过去了(时间到),如果该页还是被读取了,我才把这个页放到 new page 的首部。

  2. 通常 select * 扫描操作不会高于1秒,一个页很快就被扫完了。

1.4. Buffer Pool 的预热

1.4.1 Buffer Pool预热

  • 在MySQL启动后(MySQL5.6之前),Buffer Pool中页的数据是空的,需要大量的时间才能把磁盘中的页读入到内存中,导致启动后的一段时间性能很差

  • 使用该方法预热,强制扫描,将数据刷入buffer pool,但是不能真正将热点数据放入buffer pool ;

    • select count(1) from table force index(PRIMARY) ;
    • select count(1) from table FORCE index(index name);
  • 在 MySQL 5.6 以后,可以在 停机 的时候 dumpbuffer pool 的数据(space,page number),然后在 启动 的时候 Loadbuffer pool,该功能可以让MySQL启动时 自动预热 ,无需人工干预。
mysql gcdb@localhost:(none)> show variables like "%innodb_buffer_pool%";
+-------------------------------------+----------------+
| Variable_name                       | Value          |
+-------------------------------------+----------------+
| innodb_buffer_pool_chunk_size       | 134217728      |
| innodb_buffer_pool_dump_at_shutdown | ON             | -- 在停机时dump出buffer pool中的(space,page)
| innodb_buffer_pool_dump_now         | OFF            | -- set 一下,表示现在就从buffer pool中dump
| innodb_buffer_pool_dump_pct         | 25             | -- dump的百分比,是每个buffer pool文件,而不是整体
| innodb_buffer_pool_filename         | ib_buffer_pool | -- dump出的文件的名字
| innodb_buffer_pool_instances        | 8              |
| innodb_buffer_pool_load_abort       | OFF            |
| innodb_buffer_pool_load_at_startup  | ON             | -- 启动时加载dump的文件,恢复到buffer pool中
| innodb_buffer_pool_load_now         | OFF            | -- set一下,表示现在加载 dump的文件
| innodb_buffer_pool_size             | 8589934592     |
+-------------------------------------+----------------+
10 rows in set
Time: 0.013s
mysql gcdb@localhost:(none)>
[root@localhost-m(252) /r2/mysqldata]# head ib_buffer_pool   --dump出来的文件
2,7560
2,7557
2,7552
2,7100
2,7096
2,7092
2,7090
2,7084
2,7082
2,7077
[root@localhost-m(252) /r2/mysqldata]#
  • 1.ib_buffer_pool dump的越多,启动的越慢

  • 2.频繁的手工dump( set innodb_buffer_pool_dump_now = 1 ),会导致Buffer Pool中的数据越来越少,是因为设置了 innodb_buffer_pool_dump_pct

  • 3.如果做了高可用,可以定期dump,然后将该dump的文件传送到slave上,然后直接load( set innodb_buffer_pool_load_now = 1 )``(slave上的(Space,Page)和Master上的 大致相同 )

  • 4.load now 和 dumpnow都是 异步在后台加载的,返回的速度很快

--
--mysql 启动
--
sehll> cat error.log
## ---------------省略其他输出-----------------
2017-11-24T10:45:22.008199+08:00 0 [Note] InnoDB: Loading buffer pool(s) from /r2/mysqldata/ib_buffer_pool
## ---------------省略其他输出-----------------
2017-11-24T10:45:25.716362+08:00 0 [Note] InnoDB: Buffer pool(s) load completed at 171124 10:45:25 --速度还是很快的
--
--mysql 停机
--
shell> cat error.log
---------------省略其他输出-----------------
2017-12-29T10:31:47.844235+08:00 0 [Note] InnoDB: Dumping buffer pool(s) to /r2/mysqldata/ib_buffer_pool --dump buffer
2017-12-29T10:31:47.844597+08:00 0 [Note] InnoDB: Buffer pool(s) dump completed at 171229 10:31:47
---------------省略其他输出-----------------
  • 查看当前buffer pool中的数据的条数
[root@localhost-m(252) /r2/mysqldata]#  wc -l ib_buffer_pool
129 ib_buffer_pool
mysql gcdb@localhost:(none)> set global innodb_buffer_pool_dump_now=1;
Query OK, 0 rows affected
Time: 0.001s
mysql gcdb@localhost:(none)>  show status like \'Innodb_buffer_pool_dump_status\';
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status | Buffer pool(s) dump completed at 180105 17:49:54 |
+--------------------------------+--------------------------------------------------+
1 row in set
Time: 0.011s
mysql gcdb@localhost:(none)>
-- 已经完成
[root@localhost-m(252) /r2/mysqldata]# wc -l ib_buffer_pool
1751 ib_buffer_pool   --变为1751条
  • innodb_buffer_pool_dump_pct,该百分比(N<100)不是你当前buffer pool总的数据(总页数)N%,而是你每个buffer pool实例中最近使用的页N%

if there are 4 buffer pools with 100 pages each, and innodb_buffer_pool_dump_pct is set to 25, the 25 most recently used pages from each buffer pool are dumped

  • 查看Buffer Pool中的Flush List
SELECT
	pool_id,
	lru_position,
	space,
	page_number,
	table_name,
	oldest_modification,
	newest_modification
FROM
	information_schema.INNODB_BUFFER_PAGE_LRU
WHERE
	oldest_modification <> 0
AND oldest_modification <> newest_modification;
-- 如果没有脏页,结果集为空
-- 不要在线上操作该SQL语句,开销较大

二. Buffer Pool与压缩页

2.1 查找Buffer Pool中的压缩页

mysql gcdb@localhost:information_schema> desc information_schema.`INNODB_BUFFER_PAGE_LRU`
+---------------------+---------------------+------+-----+---------+-------+
| Field               | Type                | Null | Key | Default | Extra |
+---------------------+---------------------+------+-----+---------+-------+
| POOL_ID             | bigint(21) unsigned | NO   |     | 0       |       |
| LRU_POSITION        | bigint(21) unsigned | NO   |     | 0       |       |
| SPACE               | bigint(21) unsigned | NO   |     | 0       |       |
| PAGE_NUMBER         | bigint(21) unsigned | NO   |     | 0       |       |
| PAGE_TYPE           | varchar(64)         | YES  |     | <null>  |       |
| FLUSH_TYPE          | bigint(21) unsigned | NO   |     | 0       |       |
| FIX_COUNT           | bigint(21) unsigned | NO   |     | 0       |       |
| IS_HASHED           | varchar(3)          | YES  |     | <null>  |       |
| NEWEST_MODIFICATION | bigint(21) unsigned | NO   |     | 0       |       |
| OLDEST_MODIFICATION | bigint(21) unsigned | NO   |     | 0       |       |
| ACCESS_TIME         | bigint(21) unsigned | NO   |     | 0       |       |
| TABLE_NAME          | varchar(1024)       | YES  |     | <null>  |       |
| INDEX_NAME          | varchar(1024)       | YES  |     | <null>  |       |
| NUMBER_RECORDS      | bigint(21) unsigned | NO   |     | 0       |       |
| DATA_SIZE           | bigint(21) unsigned | NO   |     | 0       |       |
| COMPRESSED_SIZE     | bigint(21) unsigned | NO   |     | 0       |       |
| COMPRESSED          | varchar(3)          | YES  |     | <null>  |       |
| IO_FIX              | varchar(64)         | YES  |     | <null>  |       |
| IS_OLD              | varchar(3)          | YES  |     | <null>  |       |
| FREE_PAGE_CLOCK     | bigint(21) unsigned | NO   |     | 0       |       |
+---------------------+---------------------+------+-----+---------+-------+
20 rows in set
Time: 0.012s
mysql gcdb@localhost:employees> select table_name, space, page_number, index_name, compressed, compressed_size from information_schema.INNODB_BUFFER_PAGE_LRU where compressed = \'yes\' limit
                             -> 1;
+------------+-------+-------------+------------+------------+-----------------+
| table_name | space | page_number | index_name | compressed | compressed_size | --没查到很奇怪待解决。
+------------+-------+-------------+------------+------------+-----------------+
0 rows in set
Time: 0.253s
mysql gcdb@localhost:employees>

mysql gcdb@localhost:employees> create table employee_comps_2 like employees;
Query OK, 0 rows affected
Time: 0.007s
mysql gcdb@localhost:employees> insert into employee_comps_2 select * from employees;
Query OK, 300024 rows affected
Time: 2.463s
mysql gcdb@localhost:employees>  alter table employee_comps_2  row_format=compressed,key_block_size=4;
Query OK, 0 rows affected
Time: 7.972s
mysql gcdb@localhost:employees> select table_id, name, space, row_format, zip_page_size from information_schema.INNODB_SYS_TABLES where name like \'employees%\';
+----------+----------------------------+-------+------------+---------------+
| table_id | name                       | space | row_format | zip_page_size为4K |
+----------+----------------------------+-------+------------+---------------+
| 1641     | employees/departments      | 1629  | Dynamic    | 0             |
| 1643     | employees/dept_emp         | 1631  | Dynamic    | 0             |
| 1642     | employees/dept_manager     | 1630  | Dynamic    | 0             |
| 3429     | employees/employee_comps_1 | 3421  | Compressed | 16384         |
| 3433     | employees/employee_comps_2 | 3425  | Compressed | 4096          | --zip_page_size为4K
| 1640     | employees/employees        | 1628  | Dynamic    | 0             |
| 1655     | employees/newsal           | 1642  | Dynamic    | 0             |
| 1645     | employees/salaries         | 1633  | Dynamic    | 0             |
| 1644     | employees/titles           | 1632  | Dynamic    | 0             |
+----------+----------------------------+-------+------------+---------------+
9 rows in set
Time: 0.017s
mysql gcdb@localhost:employees> show create table employee_comps_2;
+------------------+-----------------------------------------------------------------------------+
| Table            | Create Table                                                                |
+------------------+-----------------------------------------------------------------------------+
| employee_comps_2 | CREATE TABLE `employee_comps_2` (                                           |
|                  |   `emp_no` int(11) NOT NULL,                                                |
|                  |   `birth_date` date NOT NULL,                                               |
|                  |   `first_name` varchar(14) NOT NULL,                                        |
|                  |   `last_name` varchar(16) NOT NULL,                                         |
|                  |   `gender` enum(\'M\',\'F\') NOT NULL,                                          |
|                  |   `hire_date` date NOT NULL,                                                |
|                  |   PRIMARY KEY (`emp_no`)                                                    |
|                  | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4 | --ROW_FORMAT压缩模式
+------------------+-----------------------------------------------------------------------------+
1 row in set
Time: 0.010s
mysql gcdb@localhost:employees>

2.2 压缩页在内存中的存放

  • 压缩页存在于 unzip_LRU 中
mysql gcdb@localhost:employees> show engine innodb status;
+--------+------+----------------------------------------------------------------------------------+
| Type   | Name | Status                                                                           |
+--------+------+----------------------------------------------------------------------------------+
| InnoDB |      |                                                                                  |
|        |      | =====================================                                            |
|        |      | 2018-01-08 10:51:59 0x7fa8c2470700 INNODB MONITOR OUTPUT                         |
|        |      | =====================================                                            |
|        |      | Per second averages calculated from the last 19 seconds                          |
-------------省略其他输出-------------
|        |      | ----------------------                                                           |
|        |      | BUFFER POOL AND MEMORY                                                           |
|        |      | ----------------------                                                           |
|        |      | Total large memory allocated 8795455488                                          |
|        |      | Dictionary memory allocated 14672808                                             |
|        |      | Buffer pool size   524224                                                        |
|        |      | Free buffers       501290                                                        |
|        |      | Database pages     19576                                                         |
|        |      | Old database pages 7063                                                          |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |
|        |      | Pending writes: LRU 0, flush list 0, single page 0                               |
|        |      | Pages made young 2, not young 0                                                  |
|        |      | 0.00 youngs/s, 0.00 non-youngs/s                                                 |
|        |      | Pages read 7594, created 11982, written 17956                                    |
|        |      | 0.00 reads/s, 0.00 creates/s, 0.00 writes/s                                      |
|        |      | No buffer pool page gets since the last printout                                 |
|        |      | Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s |
|        |      | LRU len: 19576, unzip_LRU len: 8946                                              |  -- 压缩页LRU的长度在buffer pool中的长度是8946
|        |      | I/O sum[0]:cur[0], unzip sum[0]:cur[0]                                           |
|        |      | ----------------------                                                           |
|        |      | INDIVIDUAL BUFFER POOL INFO                                                      |
|        |      | ----------------------                                                           |
|        |      | ---BUFFER POOL 0                                                                 |
|        |      | Buffer pool size   65528                                                         |
|        |      | Free buffers       62599                                                         |
|        |      | Database pages     2480                                                          |
|        |      | Old database pages 895                                                           |
|        |      | Modified db pages  0                                                             |
|        |      | Pending reads      0                                                             |
|        |      | Pending writes: LRU 0, flush list 0, single page 0                               |
|        |      | Pages made young 0, not young 0                                                  |
|        |      | 0.00 youngs/s, 0.00 non-youngs/s                                                 |
|        |      | Pages read 993, created 1487, written 1621                                       |
|        |      | 0.00 reads/s, 0.00 creates/s, 0.00 writes/s                                      |
|        |      | No buffer pool page gets since the last printout                                 |
|        |      | Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s |
|        |      | LRU len: 2480, unzip_LRU len: 1150                                               |
|        |      | I/O sum[0]:cur[0], unzip sum[0]:cur[0]                                           |
|        |      | ---BUFFER POOL 1                                                                 |
-------------省略其他输出-------------
1 row in set
Time: 0.017s
mysql gcdb@localhost:employees>

2.3 伙伴算法

磁盘中存放压缩页(row_format=compressed),假设 key_block_size=8K Buffer Pool 的页大小是 16K,向 Free List申请空闲的页,如果没有空闲页,则向 LRU List 申请页,如果LRU满了,则找LRU中最后的一个页,如果最后的页是脏页 ,则做 flush 操作,最后得到一个空白的页(16K),该16K的空白页,就给8K的压缩页使用,这样就多出一个8K的空间 ,该空间会移到8K的Free List 中去,如果有一个4K的压缩页,就把8K的Free list中的空白页给它用,然后多余的4K的空间移到4K的Free List 中去

  • 通过上述方式,不同大小的页可以在``同一个`Buffer Pool中使用 (可以简单的认为Free List是 按照页大小 来进行 划分 的)。
  • 不能根据页大小来划分缓冲池,缓冲池中页的大小就是 固定的大小( 等于innodb_page_size )
  • LRU ListFlush List 不需要按照页大小划分,都是统一的 innodb_page_size 大小

2.4. 压缩页在内存中保留

  • 被压缩的页需要在Buffer Pool中解压

  • 原来的压缩页保留 在Buffer Pool中。

  • 缺点是压缩页占用了Buffer Pool的空间,对于热点数据来说,相当于内存小了,可能造成性能下降(热点空间变小)。

    • 所以在开启压缩后,Buffer Pool的空间要相应增大
    • 如果启用压缩后节省的磁盘IO能够抵消掉Buffer Pool空间变小所带来的性能下降,那整体性能还是会上涨;
    • 启用压缩的前提是,内存尽可能的大
  • 压缩页保留的原因:

    • 是为了在更新数据的时候,将 redo 添加到压缩页的空闲部分,如果要刷回磁盘,可以直接 将该压缩页刷回去。如果该页被写满,则做一次 reorganize操作(在此之前也要做解压),真的写满了才做分裂
    • 保留压缩页是为了更快的刷回磁盘
    • 解压的页是为了更快的查询

透明压缩则没有上述压缩页的问题,因为压缩是文件系统层的,对MySQL是透明的

三 CheckPoint

3.1. CheckPoint的作用

  • 缩短数据库的恢复时间
  • 缓冲池不够用时,将脏页刷新到磁盘
  • 重做日志不可用时, 刷新脏页
  • 数据页首先被读入缓冲池中,当数据页中的某几条记录被更新或者插入新的记录时,所有的操作都是在Buffer Pool 先完成的;
    1. Buffer Pool中的某个页和磁盘中的某个页在(Space, Page_Number)上是相同的,但是其内容可能是不同的(Buffer Pool中的被更新过了),形成了脏页;
    2. 要定期将缓冲池中的脏页刷回磁盘(Flush),达到最终一致,即通过CheckPoint机制来刷脏页;

3.2. LSN (Log Sequence Number)

mysql gcdb@localhost:employees> show engine innodb status;
+--------+------+----------------------------------------------------------------------------------+
| Type   | Name | Status                                                                           |
+--------+------+----------------------------------------------------------------------------------+
| InnoDB |      |                                                                                  |
|        |      | =====================================                                            |
|        |      | 2018-01-08 11:50:58 0x7fa8c2470700 INNODB MONITOR OUTPUT                         |
|        |      | =====================================                                            |
|        |      | Per second averages calculated from the last 1 seconds                           |
-- ----------省略其他输出-------------
|        |      | ---                                                                              |
|        |      | LOG                                                                              |
|        |      | ---                                                                              |
|        |      | Log sequence number 33112246157                                                  | -- 当前内存中最新的LSN
|        |      | Log flushed up to   33112246157                                                  | -- redo刷到磁盘的LSN(不是在内存中的)
|        |      | Pages flushed up to 33112246157                                                  | -- 最后一个刷到磁盘上的页的最新的LSN(NEWEST_MODIFICATION)
|        |      | Last checkpoint at  33112246148                                                  | -- 最后一个刷到磁盘上的页的第一次被修改时的LSN(OLDEST_MODIFICATION)
|        |      | 0 pending log flushes, 0 pending chkp writes                                     |
|        |      | 439 log i/o\'s done, 0.00 log i/o\'s/second                                        |
|        |      | ----------------------                                                           |
-- LSN(Log Sequence Number) 是一个字节数

注意:

  1. Log sequence numberLog flushed up 这两个LSN可能会不同,运行过程中后者可能会小于前者,因为redo日志也是先在内存中更新,再刷到磁盘的。
  2. Pages flushed upLast checkpoint 其实都指向了最后一个刷新到磁盘的页,只是Pages flushed up 代表了页中的NEWEST_MODIFICATION ,而Last checkpoint 代表了页中的OLDEST_MODIFICATION
  • FLUSH LIST 使用OLDEST_MODIFICATION 进行记录并排序,那在刷新脏页时,CheckPointLSN 值就对应的是当前刷新到某个页的OLDEST_MODIFICATION

  • 当某个页只被修改过一次,则Pages flushed upLast checkpoint 相等,反之多次修改,则Pages flushed up 大于Last checkpoint

  • 在恢复时,从CheckPoint 开始恢复,如果当前页的LSN大于CheckPoint的LSN ,则表示不需要恢复

3.2.1 日志(redo)中的LSN:

  • 假设当前的LSN为C ,此时对某个页做修改,则会产生M 个字节的日志(需要写入M个字节的日志),那此时的LSN 则为C+M 。依次类推,LSN是一个单调递增的值(字节数)。
  • 日志中的LSN代表了日志一共写入了多少个字节。

3.2.2 页中的LSN:

  • 中也存在LSN,表示该页被修改的时候,对应的日志的LSN是多少;
  • Page中的LSN主要用在恢复的时候,Page中的LSN放在页头
mysql gcdb@localhost:employees>  desc information_schema.INNODB_BUFFER_PAGE_LRU;
+---------------------+---------------------+------+-----+---------+-------+
| Field               | Type                | Null | Key | Default | Extra |
+---------------------+---------------------+------+-----+---------+-------+
| POOL_ID             | bigint(21) unsigned | NO   |     | 0       |       |
| LRU_POSITION        | bigint(21) unsigned | NO   |     | 0       |       |
| SPACE               | bigint(21) unsigned | NO   |     | 0       |       |
| PAGE_NUMBER         | bigint(21) unsigned | NO   |     | 0       |       |
| PAGE_TYPE           | varchar(64)         | YES  |     | <null>  |       |
| FLUSH_TYPE          | bigint(21) unsigned | NO   |     | 0       |       |
| FIX_COUNT           | bigint(21) unsigned | NO   |     | 0       |       |
| IS_HASHED           | varchar(3)          | YES  |     | <null>  |       |
| NEWEST_MODIFICATION | bigint(21) unsigned | NO   |     | 0       |       | -- 该页最近一次(最新)被修改的LSN值
| OLDEST_MODIFICATION | bigint(21) unsigned | NO   |     | 0       |       | -- 该页在Buffer Pool中第一次被修改的LSN值
| ACCESS_TIME         | bigint(21) unsigned | NO   |     | 0       |       |
| TABLE_NAME          | varchar(1024)       | YES  |     | <null>  |       |
| INDEX_NAME          | varchar(1024)       | YES  |     | <null>  |       |
| NUMBER_RECORDS      | bigint(21) unsigned | NO   |     | 0       |       |
| DATA_SIZE           | bigint(21) unsigned | NO   |     | 0       |       |
| COMPRESSED_SIZE     | bigint(21) unsigned | NO   |     | 0       |       |
| COMPRESSED          | varchar(3)          | YES  |     | <null>  |       |
| IO_FIX              | varchar(64)         | YES  |     | <null>  |       |
| IS_OLD              | varchar(3)          | YES  |     | <null>  |       |
| FREE_PAGE_CLOCK     | bigint(21) unsigned | NO   |     | 0       |       |
+---------------------+---------------------+------+-----+---------+-------+
20 rows in set
Time: 0.014s
mysql gcdb@localhost:employees>

3.2.3 CheckPoint LSN

每个数据库中也有一个LSN,表示最后一个刷新到磁盘的页的LSN ,表明了该LSN之前的数据都刷回到磁盘了,且如果要做恢复操作,也只要从当前这个CheckPoint LSN 开始恢复。

CheckPoint LSN 写在redo log 的前2K 空间中:

  1. 日志中的LSN = CheckPoint的LSN ,则表示所有页都已经刷回磁盘
  2. 日志中的LSN > CheckPoint的LSN ,则表示还有页没刷到磁盘;如果是宕机,则需要用日志恢复。
  3. 日志中的LSN < CheckPoint的LSN ,则报错

3.3 CheckPoint的分类

  • Sharp CheckPoint

    • 所有的脏页刷新回磁盘
    • 通常在数据库关闭的时候
    • 刷新时系统hang住
    • innodb_fast_shutdown={1|0}
  • Fuzzy CheckPoint

    • 部分脏页刷新回磁盘
    • 对系统影响较小
  • innodb_io_capacity

    • 最小限制为100
    • 一次最多刷新脏页的能力,与IOPS相关
      • SSD 可以设置在4000-8000
      • SAS 最多设置在`800多``(IOPS在1000左右)

3.4. 刷新

  • Master Thread Checkpoint

    • FLUSH_LIST 中刷新
  • FLUSH_LRU_LIST Checkpoint

    • LRU_LIST 中刷新(即使不在脏页链表中)

      • 5.5以前需要保证在 LRU_LIST 尾部要有100个空闲页(可替换的页),即 刷新一部分数据 ,保证有100个空闲页
    • innodb_lru_scan_depth – 每次进行 LRU_LIST 刷新的脏页的数量

      • 应用到每个Buffer Pool实例,总数即为该值乘以Buffer Pool的实例个数,如果超过 innodb_io_capacity 是不合理的
      • 建议该值不能超过 innodb_io_capacity / innodb_buffer_pool_instances
  • Async/Sync Flush Checkpoint

  • 重做日志重用
  • Dirty Page too much Checkpoint

    • innodb_max_dirty_pages_pct 参数控制

四. Double Write

4.1 Double Write介绍

  • Double Write的目的是为了保证数据写入的可靠性,避免partial write的情况

    • partial write(*部分写*)
      • 16K的页只写入了4K6K8K12K的情况(此时是不完整、不干净的页);
      • 不可以通过redo log进行恢复;
      • redo恢复的前提是该必须是完整、干净的;
  • Double Write全局的;

  • 共享表空间存在一个段对象 double write,然后这个段由2个区(1M)组成

  • 2M固定大小(both file and memory);

  • 页在刷新时,首先顺序 的写入到double write

  • 然后再刷回磁盘(ibd

有点类似 RAID-1 的机制,总有一份数据是正确的
简单说来,就是在脏页刷新到磁盘前,先要有个地方记录这个脏页的副本

1.将脏页copyDouble Write Buffer对象中,默认2M大小;

2.将Double Write Buffer中的对象先写入 到共享表空间(ibdata1)中的Double Write

  • 2M循环覆盖
  • 顺序写入(一次IO)

3.再根据(spacepage\\_no)写入到原来的ibd文件中;

4.如果是在写到ibdata1中的Double Write时,发生宕机;此刻原来的ibd file 仍然是完整、干净的 ,下次启动后是可以用redo文件进行恢复的。

5.如果是写到ibd文件时,发生了宕机;此刻在原来的ibdata1中存在副本,可以直接覆盖到ibd文件(对应的页)中去,然后再进行redo进行恢复

redo物理逻辑的,物理表示记录的日志针对的是页(page)的修改,逻辑表示记录日志的内容是逻辑的。

mysql gcdb@localhost:employees>  show variables like "%doublewrite%";
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| innodb_doublewrite | ON    |
+--------------------+-------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:employees> 

4.2 Double Write的开销

  • Double Write2M数据是顺序刷入磁盘的,是 一次IO ,该次IO的大小为2M
  • 开启Double Write的性能降低5% \\~ 25%IO Bound场景下降的最厉害)
  • slave服务器同样 需要开启

4.3.Double Write可以关闭的前提

4.3.1.支持原子 写的设备

  • 磁盘
    • Funsion-IO
    • 宝存
  • 文件系统
    • ZFS (Linux上不推荐使用)
    • btrfs(Linux上不推荐使用)
      • 使用copy on wirte机制,不进行原地更新 ,而是开辟新的位置,写成功后,将原来的页释放
      • 本质上的思路还是保留一个副本

4.3.2. innodb_doublewrite参数

  • innodb_doublewrite=0 可以关闭double write功能

五. Insert/Change Buffer

5.1. Insert/Change Buffer介绍

  • MySQL5.5版本以前叫做insert buffer,现在叫做change buffer
  • 提高辅助索引的插入性能
  • 非唯一的二级索引(non-unique secondary index
  • Insert/Change Buffer 是一个持久化的对象(在ibdata1中,同时也会写redo日志)`
  • Insert/Change Buffer页是一棵B+树,每次最缓存2K的记录`
  • 开启后有30%的性能提升(默认开启)`
  • MySQL5.5版本之前,最大可以设置为Buffer Pool1/2,现在最大只能设置为` 1/4
  • Insert Buffer进行合并 的时候,`性能下降
mysql gcdb@localhost:employees>  show variables like "%change_buffer%";
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| innodb_change_buffer_max_size | 25    |
| innodb_change_buffering       | all   |
+-------------------------------+-------+
2 rows in set
Time: 0.012s

5.2. Insert/Change Buffer举例

CREATE TABLE t (
a INT AUTO_INCREMENT, -- a 列是自增的
b VARCHAR(30),   -- b 列是varchar
PRIMARY KEY(a)   -- a 是主键
key(b)           -- b 是二级索引(如果是name之类的,可以看成是非唯一的)
);
  • 对于主键 a列),每次插入都要立即插入对应的聚集索引 页中(在内存中就直接插入,不在内存就先读取到内存)

  • 对于二级索引 secondary index)(b列

  1. 没有 Insert/Change Buffer时,每次插入一条记录,就要读取一次页(读取内存,或者从磁盘读到内存),然后将记录插入到页中;

  2. 有 Insert/Change Buffer时,当插入一条记录时,先判断 记录对应要插入的二级索引 (secondary index)页是否 Buffer Pool中:

    • 如果该二级索引 (secondary index)页已经在Buffer Pool中 ,则直接插入
    • 反之,先将其Cache起来,放到Insert/Change Buffer中,等到该二级索引 (secondary index)页被读到 时,将Insert/Change Buffer中该页对应的记录合并 Merge)进去,从而减少I/O操作;

Insert/Change Buffer就是用来提升二级索引插入的性能使用空间换时间,批量插入的方式(二级索引可以不急着插入,只要主键已经插入了即可)

5.3. Insert/Change Buffer 性能

  • 1.左图使开启了Insert/Change Buffer,而右图未开启;
  • 2.一开始都比较高是因为还没有全量的进行刷磁盘(脏页全部在Buffer Pool中,还没满)`
    • 如开始介绍时所说,当Insert Buffer进行合并的时候,性能进行下降
  • 3.开启Insert/Change Buffer后,insert的常量值在5K左右;
  • 4.SSD场景下也建议开启;

5.4. Insert/Change Buffer 查看

mysql gcdb@localhost:employees> show engine innodb status;
+--------+------+----------------------------------------------------------------------------------+
| Type   | Name | Status                                                                           |
+--------+------+----------------------------------------------------------------------------------+
| InnoDB |      |                                                                                  |
|        |      | =====================================                                            |
|        |      | 2018-01-08 14:39:01 0x7fa8c2470700 INNODB MONITOR OUTPUT                         |
|        |      | =====================================                                            |
|        |      | Per second averages calculated from the last 18 seconds                          |
|        |      | -----------------                                                                |
|        |      | BACKGROUND THREAD                                                                |
|        |      | -----------------                                                                |
|        |      | srv_master_thread loops: 175 srv_active, 0 srv_shutdown, 876729 srv_idle         |
|        |      | srv_master_thread log flush and writes: 876841                                   |
|        |      | ----------                                                                       |
-----------省略其他输出-------------
|        |      | -------------------------------------                                            |
|        |      | INSERT BUFFER AND ADAPTIVE HASH INDEX                                            |
|        |      | -------------------------------------                                            |
|        |      | Ibuf: size 1, free list len 0, seg size 2, 2 merges                              |
|        |      | merged operations:                                                               |
|        |      |  insert 0, delete mark 2, delete 0                                               |
|        |      | discarded operations:                                                            |
|        |      |  insert 0, delete mark 0, delete 0                                               |
|        |      | Hash table size 2656009, node heap has 0 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 0 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 1 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 0 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 1 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 1 buffer(s)                               |
|        |      | Hash table size 2656009, node heap has 1 buffer(s)                               |

1.seg size:的数量,例如当前页为8K,则seg\\_size \\* 8K就是Insert/Change Buffer使用的内存大小;
2.merges:合并了多少
3.merged insert:插入了多少条记录

  • insert / merges `就是插入的效率(插入一条记录,就要读取一次页);

4.discarded operations:应该是很小的值,或者为0;当记录写入到Insert/Change Buffer后,对应的表被删除了,则相应的Buffer中的记录就应该被丢弃;

使用Insert/Change Buffer的前提时,需要使用随机IO ,这时才放入Buffer中,如果页已经在Buffer Pool(内存)中,就不需要 使用Insert/Change Buffer了`

5.5.Change Buffer

  • MySQL 5.5 以后,改名为Change Buffer,表示不仅仅适用于insert。`

      1. Insert
      1. Delete-Marking(标记删除)
      1. Purge(清除)
      1. innodb_change_buffering = all
      • all
      • none (禁用)
      • inserts
      • deletes
      • changes =(insert & delete-marking)
      • purge

六. Adaptive Hash Index(自适应Hash索引)