1. 程式人生 > >緩沖池工作原理淺析

緩沖池工作原理淺析

元數據 end master emp rim sel 導致 啟動 root

Ⅰ、緩沖池介紹

innodb存儲引擎緩沖池(buffer pool) ,類似於oracle的sga,裏面放著數據頁 、索引頁 、change buffer 、自適應哈希 、鎖(5.5之前)等內容

技術分享圖片

綜上所示:

  • 每次讀寫數據都是通過Buffer Pool
  • 當Buffer Pool中沒有用戶所需要的數據時才去硬盤中獲取
  • 通過innodb_buffer_pool_size進行設置總容量,該值設置的越大越好

Ⅱ、緩沖池性能問題

2.1 性能線性擴展

假設服務器72核,ht超線程後,144個邏輯核,跑測試按道理144個核應該跑滿,如果跑不滿,就說明並發有瓶頸,我加核了,卻用不上,性能上不去

5.1之前這個問題經常被吐槽,現在不存在這個問題了

1G空間下面如果有65536個page,對這些page進行管理,每次都要對bp加鎖(latch),如果bp大了,就有瓶頸,這裏說的鎖是bp的latch,和數據庫的lock不是一回事

qps達到1w,每秒鐘要獲得至少1w次latch(就看bp的latch,不談釋放和喚醒latch),開銷比較大

核比較多,latch或者並發設計的不好,性能則不能線性擴展 ,而這個bp對於擴展性非常重要,所有的熱點的page都在裏面,每次訪問這些page都要獲得bp的latch

2.2 如何提升上述緩沖池性能問題

調整innodb_buffer_pool_instances參數,設置為cpu的數量

默認5.5為1,5.6和5.7是8

假設開始這個值是1,現調整為4,原來1個bp管理65536個頁,現在4個bp,每個bp管理16384個頁,拆成4個分片,將熱點打散,latch變少了,並發性能提升了,這是非常常見的內核層對並發調優的手段,經測試,不調整與調整後性能相差30%

tips:

設置多個緩沖池的時候,必須滿足每個池子大於1G才生效,否則,即使my.cnf中設置了innodb_buffer_pool_instances,重啟看看是沒用的

Ⅲ、buffer pool中熱點數據的管理

3.1 buffer pool的組成

技術分享圖片

緩沖池中的熱點是以page為單位來管理,並不是三種List加起來等於總的bp大小,而是Free List + LRU List(Flush List是包含在LRU list裏面的)

  • Free List 放空白的page

buffer Pool剛啟動時,有一個個16K的空白的頁,這些頁就存放(鏈表串聯)在Free List中

  • LRU List 包括LRU和unzip_LRU

當讀取一個數據頁的時候,就從Free List中取出一個頁,存入數據,並將該頁讀到LRU List中

當Free List給一個頁給LRU List時,這個過程中需要一個並發控制,也就是之前說的latch,假設現在有兩個線程都讀到磁盤上這個頁,則都需要問Free List來申請空閑頁,誰先來先給誰,latch就是對這三個List進行並發控制訪問的

  • Flush List包含臟頁(數據經過修改,但是未刷入磁盤的頁),根據oldest_lsn進行排序

假設被讀到的頁,馬上被更新,這個頁就叫臟頁,會被放入到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消耗比較大

tips:

Flush list 中存放的不是一個頁,而是頁的指針(page number)

小結:

LRU List存放的是所有已經使用的頁,裏面既有幹凈頁也有臟頁,Flush List中只有指向臟頁的指針

3.2 查看buffer pool的狀態

方法1:show engine innodb status\G

...
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 303387
Buffer pool size   8192     #緩沖池中共8192個page
Free buffers       7772     #空白頁(Free List),線上很可能是0
Database pages     420      #在使用的頁(LRU List)
Old database pages 0        #LRU中教冷的page
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    #youngs表示old變為new
Pages read 368, created 52, written 322
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: 420, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
...

如果設置了多個buffer pool
找到individual buffer pool info看每一個bp的情況

方法2:看兩張元數據表

先說下,這東西比較大,看起來不是很方便,不太推薦

root@localhost) [(none)]> SELECT
    ->     pool_id,
    ->     pool_size,
    ->     free_buffers,
    ->     database_pages,
    ->     old_database_pages,
    ->     modified_database_pages
    -> FROM
    ->     information_schema.innodb_buffer_pool_stats\G
*************************** 1. row ***************************
                pool_id: 0
              pool_size: 8192
           free_buffers: 7772
         database_pages: 420
     old_database_pages: 0
modified_database_pages: 0
1 row in set (0.00 sec)

(root@localhost) [(none)]> SELECT
    ->     space, page_number, newest_modification, oldest_modification
    -> FROM
    ->     information_schema.innodb_buffer_page_lru
    -> LIMIT 1\G
*************************** 1. row ***************************
              space: 0
        page_number: 7
newest_modification: 5330181742     #該頁最近一次(最新)被修改的LSN值
oldest_modification: 0              #該頁在Buffer Pool中第一次被修改的LSN值,FLush List是根據該值進行排序的,該值越小,表示該頁應該最先被刷新
1 row in set (0.01 sec)

3.2 LRU算法解析

MySQL中使用了midpoint LRU算法來管理LRU List

技術分享圖片

  • 當該頁被第一次讀取時,將該頁先放在mid point的位置(因為無法保證一定是活躍)
  • 當被讀到第二次時才將改頁放入到new page的首部
  • innodb_old_blocks_pct參數控制mid point的位置,默認是37,即3/8的位置

3.3 緩沖池防汙染

有一種場景,某個page一下子被掃了n次,但其實他並不是熱頁,這時候如果按照之前說的,這個page會被放到new裏面去,這其實就汙染了緩沖池

那什麽時候會出現一個page每秒被讀n次呢?

scan的時候,select * from tb_name;如果這個page裏有10條記錄,這個page就會被讀10次

我們可以通過將一個page固定在midpoint位置一定的時間來解決這個問題

set global innodb_old_blocks_time=1;

通常 select * 掃描操作不會高於1秒,一個頁很快就被掃完了

無論讀多少次,在innodb_old_blocks_time的時間內都不管(都視作只讀取了一次),等這個時間過去了(時間到),如果該頁還是被讀取了,才把這個頁放到new page的首部,如果設為0,則表示讀到第二次就放到new裏去

如果開發有個scan操作,就需要設置一下,操作完後再改回來。最好的方案是放到從機上,避免掃描語句汙染LRU

tips:

①如果一個page中10條記錄一次讀,讀這十條記錄的時候這個頁就會被鎖成只讀,那其他線程對這個頁的操作就不被允許了,數據庫是一個並發系統,這是不合理的,這樣讀一個頁hold住鎖的時間會長,所以是每讀一條記錄去讀一次頁,然後馬上釋放,把讀到的位置————遊標(這個遊標和數據庫的遊標不是一回事)保存下來,下次再要讀的時候,從打開這個遊標繼續讀,但是位置可能會變化,所以會重新去讀這個頁,以此確保各個線程公平調度

②myisam緩存data是交給操作系統緩存 ,和pg一樣

3.4 buffer pool的預熱

背景:

在MySQL啟動後(MySQL5.6之前),Buffer Pool中頁的數據是空的,需要大量的時間才能把磁盤中的頁讀入到內存中,導致啟動後的一段時間性能很差

例:啟動的時候load

64GB BP 10M/s讀取 100min

預熱策略:將LRU列表dump出來,通過較順序讀取的方式預熱50M~200M

預熱方法:

select count(1) from table force index(primary)

select count(1) from index

說明:

上面兩種方法很痤。並沒有預熱真正的熱點數據,只是把數據讀進來了,粒度非常粗,比如你數據100G,bp10G,那真正的熱點很大部分不是熱點數據

網易試過共享內存來做,數據庫重啟bp不清,不過操作系統重啟了也就白搭了

好辦法:

MySQL5.6 開始有辦法了

(root@172.16.0.10) [(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的bp的前百分之多少,是每個buffer pool最近使用的頁數,而不是整體,可寫到[mysqld-5.7]中
| innodb_buffer_pool_filename         | ib_buffer_pool |    #dump出的文件的名字
| innodb_buffer_pool_instances        | 1              |
| 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             | 1879048192     |
+-------------------------------------+----------------+
10 rows in set (0.00 sec)
  • 關閉數據庫之前把bp中的space和page_no給dump出來(不是整個bp,5.6還沒正式發布的時候就是dump所有)
  • 重啟的時候會把dump出來的內容load進bp,dump出來是無序的,load之前根據space和pageno進行排序,load是異步的,返回速度還可以,對bp基本沒影響
  • dump的越多,啟動的越慢
  • 頻繁dump會導致Buffer Pool中的數據越來越少,是因為設置了innodb_buffer_pool_dump_pct,默認25,姜總用的40
  • 如果做了高可用,可以定期dump,然後將該dump的文件傳送到slave上,然後直接load(slave上的(Space,Page)和Master上的 大致相同 )

簡單演示一把:

(root@localhost) [(none)]> set global innodb_buffer_pool_dump_now = 1;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [(none)]>  show status like ‘Innodb_buffer_pool_dump_status‘;
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status | Buffer pool(s) dump completed at 180302 16:57:45 |
+--------------------------------+--------------------------------------------------+
1 row in set (0.00 sec)

進入數據目錄
[root@VM_0_5_centos data3306]# ll *pool
-rw-r----- 1 mysql mysql 604 Mar  2 16:59 ib_buffer_pool
[root@VM_0_5_centos data3306]# head ib_buffer_pool
0,568
0,567
0,566
0,565
0,278
0,564
0,563
0,562
164,3
164,2

停止服務
[root@VM_0_5_centos data3306]# mysqld_multi stop 3306
截取錯誤日誌
2018-03-02T09:01:10.292549Z 0 [Note] InnoDB: Starting shutdown...
2018-03-02T09:01:10.392851Z 0 [Note] InnoDB: Dumping buffer pool(s) to /mdata/data3306/ib_buffer_pool
2018-03-02T09:01:10.393059Z 0 [Note] InnoDB: Buffer pool(s) dump completed at 180302 17:01:10

啟動服務,加載熱數據
[root@VM_0_5_centos data3306]# mysqld_multi start 3306
(root@localhost) [(none)]> set global innodb_buffer_pool_load_now = 1;
Query OK, 0 rows affected (0.00 sec)

再截取錯誤日誌
2018-03-02T09:06:40.526294Z 0 [Note] InnoDB: Loading buffer pool(s) from /mdata/data3306/ib_buffer_pool
2018-03-02T09:06:40.526487Z 0 [Note] InnoDB: Buffer pool(s) load completed at 180302 17:06:40

tips:

註意一下innodb_buffer_pool_dump_pct這個參數,先看下下面這個流程

(root@localhost) [(none)]> set global innodb_buffer_pool_dump_pct=100;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [(none)]> set global innodb_buffer_pool_dump_now = 1;
Query OK, 0 rows affected (0.00 sec)

[root@VM_0_5_centos data3306]# cat ib_buffer_pool |wc -l
576

(root@localhost) [(none)]> set global innodb_buffer_pool_dump_pct=20;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [(none)]> set global innodb_buffer_pool_dump_now = 1;
Query OK, 0 rows affected (0.00 sec)

[root@VM_0_5_centos data3306]# cat ib_buffer_pool |wc -l
115

看上去沒啥問題,但要註意的是,當你有多個緩沖池的時候,比如有4個,每個裏面有100個page,它不是整體來dump前百分之25,而是dump每個緩沖池裏面最前面的15個page

Ⅳ、異步讀

發現全表掃描,如果已經掃了一部分內容,innodb會異步讀取這部分內容後面的一部分,即使你沒讀到,異步讀有兩種情況,如下:

隨機預讀
innodb_random_read_ahead
線性預讀
innodb_read_ahead_threshold    該參數目前缺省值為0
  • 線性預讀放到以extent為單位,而隨機預讀放到以extent中的page為單位
  • 線性預讀是將下一個extent提前讀取到buffer pool中,隨機預讀是將當前extent中的剩余的page提前讀取到buffer pool中

緩沖池工作原理淺析