一. 缓冲池(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的状态
- 使用命令 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)
- 使用元数据表 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)>
当该页被第一次
读取时
,将该页放在mid point
位置,但是随后无论你读多少次 ,我在这innodb_old_blocks_time
的时间内都不管(都视作只读取了一次 ),等这个时间过去了(时间到),如果该页还是被读取
了,我才把这个页放到new page
的首部。通常
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 以后,可以在
停机
的时候dump
出buffer pool
的数据(space,page number),然后在启动
的时候Load
进buffer 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 List
和Flush 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 先完成的;- Buffer Pool中的某个页和磁盘中的某个页在
(Space, Page_Number)
上是相同的,但是其内容可能是不同的
(Buffer Pool中的被更新过了),形成了脏页; - 要定期将缓冲池中的脏页刷回磁盘
(Flush)
,达到最终一致,即通过CheckPoint机制
来刷脏页;
- Buffer Pool中的某个页和磁盘中的某个页在
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) 是一个字节数
注意:
Log sequence number
和Log flushed up
这两个LSN可能会不同,运行过程中后者可能会小于前者,因为redo日志也是先在内存中更新,再刷到磁盘的。Pages flushed up
与Last checkpoint
其实都指向了最后一个刷新到磁盘的页,只是Pages flushed up
代表了页中的NEWEST_MODIFICATION
,而Last checkpoint
代表了页中的OLDEST_MODIFICATION
。
FLUSH LIST
使用OLDEST_MODIFICATION
进行记录并排序,那在刷新脏页时,CheckPoint
的LSN 值
就对应的是当前刷新到某个页的OLDEST_MODIFICATION
;当某个页只被修改过一次,则
Pages flushed up
与Last 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 空间中
:
- 日志中的LSN = CheckPoint的LSN ,则表示所有页都已经刷回磁盘
- 日志中的LSN > CheckPoint的LSN ,则表示还有页没刷到磁盘;如果是宕机,则需要用日志恢复。
- 日志中的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个空闲页
- 5.5以前需要保证在
- innodb_lru_scan_depth – 每次进行
LRU_LIS
T 刷新的脏页的数量
- 应用到每个
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
的页只写入了4K
,6K
,8K
,12K
的情况(此时是不完整、不干净的页);不可以
通过redo log
进行恢复;redo
恢复的前提是该页
必须是完整、干净
的;
- partial write
Double Write
是全局
的;- 共享表空间存在一个
段对象 double write
,然后这个段由2个区(1M)组成
2M
固定大小(both file and memory
);- 页在刷新时,首先
顺序
的写入到double write
然后再刷回磁盘(
ibd
)
有点类似
RAID-1
的机制,总有一份数据
是正确的
简单说来,就是在脏页刷新到磁盘前
,先要有个地方记录这个脏页的副本
1.将脏页copy
到Double Write Buffer
对象中,默认2M
大小;
2.将Double Write Buffer
中的对象先写入
到共享表空间(ibdata1
)中的Double Write
;
- 2M循环覆盖
- 顺序
写入
(一次
IO)
3.再根据(space
,page\_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 Write
的2M
数据是顺序
刷入磁盘的,是一次
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
机制,不进行原地更新
,而是开辟新的位置,写成功后,将原来的页释放
- 本质上的思路还是
保留一个副本
- 使用
- ZFS (
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 Pool
的1/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列
)
在
没有 Insert/Change Buffer
时,每次插入一条记录,就要读取一次页
(读取内存,或者从磁盘读到内存),然后将记录插入到页中;- 在
有 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
。`-
- Insert
-
- Delete-Marking
(标记删除)
- Delete-Marking
-
- Purge
(清除)
- Purge
-
- innodb_change_buffering = all
- all
- none
(禁用)
- inserts
- deletes
- changes =
(insert & delete-marking)
- purge
-
六. Adaptive Hash Index(自适应Hash索引)
搜索的时间复杂度
B+
树O(T)
,只能定位到该记录所在的页;哈希表
O(1)
,可以直接定位到记录;
- 可以自己判断是否是活跃的页,如果是活跃的页,可以自动做
Hash
,管理员无需人工干预; MySQL5.6
版本后,官方不推荐
使用自适应Hash
索引- CPU
使用率变高`,但是性能没有提升;MySQL5.7
中增加了innodb_adaptive_hash_index_parts
,增加分片,以减少竞争;只对等值的操作有意义
;
七. Flush Neighbor Page (FNP)
- 刷新
脏页所在区
(extent
)的所有脏页
,合并IO
,随机转顺序的优化;写入的数据太多
如果业务确实是频繁更新,那刷新也会很频繁
- 对传统机械磁盘有意义;
- innodb_flush_neighbors={0|1|2}
(
>=MySQL 5.6)
- 0
:表示关闭该功能
- 1
:表示刷新一个区内的脏页
- 2
:表示刷新几个
连续的脏页
- innodb_flush_neighbors={0|1|2}
SSD
建议关闭次功能;
mysql gcdb@localhost:employees> show variables like "%flush_neigh%";
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_flush_neighbors | 1 | -- 非SSD建议使用2
+------------------------+-------+
1 row in set
Time: 0.012s
mysql gcdb@localhost:employees>