參考:
https://blog.csdn.net/weixin_38304221/article/details/88635432
https://blog.csdn.net/nanjingitany/article/details/102856472
https://www.cnblogs.com/abclife/p/7625281.html
https://www.cnblogs.com/ging/p/13467833.html
MySQL中的change buffer
MySQL的一條語句,大致流程查看內存→讀取磁盤數據頁→返回數據。
當比如查找一個 a=5的記錄的時候,並不是只查找出這一條數據,它所在的整個數據頁都會查找出來(每個數據頁16KB)。
下次查找a=6的記錄的時候,發現該頁已經在內存中了,直接返回,不需要磁盤IO。
但是當時增、刪、改操作時,並不會每一次操作都進行一次磁盤IO,使用change buffer可以降低磁盤隨機IO。
change buffer首先是可持久化的數據。
當更新某個數據頁時,該頁在內存中,那么直接更新。
如果該頁不在內存中,那么先將更新操作記錄在change buffer中,這時不需要從磁盤中讀出數據頁。
將操作記錄到redo log中,防止機器意外關閉導致數據丟失。
這時已經可以返回給客戶端更新成功了,因為即使機器意外重啟,也可以通過redo log找回數據。
change buffer 使用的是 buffer pool里的內存,不能無限增大,可以通過參數 innodb_change_buffer_max_xize來動態設置,這個參數為50的時候,標識change buffer 的大小最多只能占用 buffer pool 的50%。
什么時候將change buffer中的數據更新到磁盤中?
當下一次查詢命中這個數據頁的時候,會先從磁盤中讀取數據頁到內存中,然后先執行change buffer的merge操作,保證數據邏輯的正確性。除了查詢操作外,系統有后台線程會定期merge,數據庫正常關閉(shutdown)的時候,也會進行merge操作。
change buffer 和 redo log 對於磁盤的隨機IO影響。
redo log是減少 隨機寫磁盤IO 的消耗。每個操作先記錄redo log,系統空閑時或redo log滿時進行磁盤IO。
change buffer是減少 隨機讀磁盤IO 的消耗。更新時如果內存中不存在該數據頁,也不需要馬上進行磁盤IO,而是先記錄在change buffer中,等待時機統一merge。
所以根據業務場景選擇是否使用change buffer。
當寫少讀多的情況下:change buffer的利用率不高,因為可能剛一更新完可能就觸發merge操作,change buffer沒有起到減少隨機IO,還多了一個維護change buffer的成本。
當寫多讀少的情況下:change buffer的提升效果較明顯,可能很多條更新后,也沒有一個查詢線程來觸發merge操作,可以大幅減少磁盤的隨機IO。
唯一索引和 普通索引(在業務邏輯層保證數據唯一)的選擇
命中唯一索引和普通索引時MySQL篩選數據的邏輯不同。
比如一個查詢語句 select * from table where k=5;先通過B+樹從樹根開始,按層搜索到葉子節點,也就是數據頁。
唯一索引 k:由於索引定義了唯一性,所以在數據頁中找到第一個滿足條件的記錄后,直接返回。
普通索引 k:沒有定義索引的唯一性,所以在查找到第一條滿足k=5后,還會繼續遍歷下一條,直到查到的k != 5的時候,結束遍歷。
那么兩種索引的效率差距有多大呢?
其實是很小,幾乎可忽略。普通索引相對於唯一索引多的一步操作就是"查找並判斷下一條記錄",因為都存在於同一個數據頁,所以內存遍歷對於CPU的開銷可忽略。一個數據頁可以存在上千個key,剛好下一條數據在下一個數據頁而觸發再次讀取數據頁的概率很低。所以這兩種索引的效率幾乎一樣。
但是當進行增刪改操作時,兩種索引的效率就有明顯的差距了。
因為唯一索引每個插入或者更新都要判斷是否違反唯一約束,所以每次更新都要從內存中找到這個記錄對應的數據頁,沒有的話需要從磁盤中讀出,反正都要從磁盤讀數據頁了,還不如直接更新內存/磁盤,那么也就意味着change buffer失去意義,所以唯一索引使用change buffer 的話反倒會降低效率,所以只有普通索引能使用到change buffer。
淺談mysql的change buffer
1.數據的更新過程
當mysql執行更新操作時,需要先更新一個數據頁。如果這個數據頁在內存中,則會直接更新,如果
這個數據頁不在內存中,InnoDB會將這個更新操作緩存在change buffer中,當下次需要查詢訪問這個數據頁的時候,會將數據頁讀入內存,然后執行change buffer中和這個數據頁相關的操作,這樣就保證了數據的邏輯正確性。
2.merge觸發
將change buffer中的操作應用到原數據頁,得到最新結果的過程稱為merge。除了訪問這個數據頁會觸發merge外,系統有后台線程會定期merge。在數據庫正常關閉(shutdown)的過程中,也會執行merge操作。
3.使用change buffer的場景
只有普通索引的更新會使用change buffer,用來提高內存利用率,但是如果被更新的字段是唯一索引,就不會使用change buffer,因為更新操作會判斷是否違反唯一約束的條件,必須將數據頁讀入內存才能判斷,因此唯一索引不會使用change buffer,只有普通索引才能使用。
在日常業務場景中,對於寫多讀少的操作,使用change buffer的效果最好,例如賬單類、日志類的操作,如果是讀寫操作都是很多的情況下,寫完后將操作記錄保存在change buffer中然后立即查詢,這樣會頻繁觸發merge,增加了IO的操作頻率,這種場景使用change buffer並不能帶來性能的提升,這種業務場景應該關閉change buffer。
4.change buffer的大小設置
在InnoDB中,可以通過參數innodb_change_buffer_max_size來設置,默認值是25,最大是50。例如當這個參數值是50 的時候,表示change buffer最多只能占用buffer pool的50%
5.索引的選擇
唯一索引和普通索引在查詢上並無區別,但是在更新操作上兩者對性能的影響是不一樣的,所以建議盡量使用普通索引。
MySQL-對Change Buffer的理解
Change Buffer的處理過程
對非唯一的普通索引的新增或更新操作,如果索引B+樹的需要新增或更新的數據頁不在內存中,則直接更新change buffer,等到后面需要使用這個數據頁(真正讀到內存中來)的時候,再根據change buffer在內存中做merge合並操作。
Change Buffer有什么好處?
先想想沒有change buffer時候,在緩沖池中沒有對應數據頁時會怎么更新。概括來說,有兩個步驟:
- 首先需要從磁盤中讀取對應的數據頁到內存中
- 然后更新內存中的數據頁。
首先分析主鍵索引或者非主鍵索引中的唯一索引,插入或者更新的操作。
- 主鍵如果是自增的,只需要讀取順序讀取磁盤中的頁,然后插入最新的行即可。主鍵如果是非自增的或者是自己設置的值,那么可能需要做一次隨機磁盤IO操作,讀取到對應的頁,做一下唯一性判斷,然后插入數據即可。
- 針對非主鍵索引中的唯一索引,大概率需要做隨機磁盤IO讀取,然后判斷唯一性,再插入對應的行。
所以對於主鍵索引和非主鍵的唯一索引,因為有唯一性判斷,所以更新操作時,必須要從磁盤中讀取數據頁,判斷唯一性,然后才能確定這個更新操作是否成功,即這個磁盤的IO操作是不可避免的。
對於非唯一索引來說,其實步驟也是類似的。但是因為不需要做唯一性判斷,所以為了提高更新的性能,Mysql給出的解決方案就是使用change buffer來保存對非唯一索引的更新。也就是說,當需要更新非唯一索引時,直接操作change buffer,成功即可返回。
那么什么時候會真正更新數據頁呢?有兩種情況會觸發:
- 被動:在后續的真正需要讀這個非唯一索引時,把索引的數據頁從磁盤讀取到內存中,再通過change buffer做一個merge操作,merge操作以后,內存中的數據頁就是最新的了。
- 主動:innoDB引擎中有線程會主動的定期做merge操作
業務實踐
- 利用普通索引的change buffer特性,當業務場景中的寫遠大於讀時,常見場景為日志表,當某些列必須建立索引時,可以考慮建立普通索引,提高寫入性能。
- 如果業務場景的寫之后立即伴隨讀,如果列的值是唯一的
- 那么其實建立普通索引是不合適的,因為寫的過程,雖然利用了change buffer暫時提高了寫的性能,但是在讀的時候還是需要磁盤IO。可以考慮建立唯一索引,在索引寫的時候,就提前讀取數據到緩沖池中,提高讀的性能。
MySQL -- Innodb中的change buffer
change buffer是一種特殊的數據結構,當要修改的輔助索引頁不在buffer pool中時,用來cache對輔助索引頁的修改。對輔助索引頁的操作可能是insert、update和delete操作。等到相關的索引頁被讀入buffer pool中后,才會使用change buffer中的內容對輔助索引頁進行修改(即merge操作)。
和聚集索引不同,輔助索引通常是不唯一的,插入輔助索引通常也是隨機的。同樣,對輔助索引的刪除、更新也通常是不連續的。
等到相關的索引頁被讀入buffer pool中后,才會使用change buffer中的內容對輔助索引頁進行修改(即merge操作)可以避免大量的磁盤隨機訪問I/O。
間歇性的,在系統空閑或關閉過程中,會執行purge操作,將新的索引頁寫入磁盤。purge操作一次寫多個索引值會比每次修改后就立即寫入磁盤的效率高。
對change buffer的merge可能需要好幾個小時,如果被更新的輔助索引行比較多。在merge過程中,磁盤的I/O會增加,可能會引起其他查詢的性能的降低。
merge操作也可能發生在事務提交后。事實上,即使在實例重啟后,還會可能發生merge操作。
在內存中,change buffer會占用buffer pool的空間;在物理磁盤上,change buffer是system tablespace的一部分,所以對索引的修改在數據庫重啟后仍然存在change buffer中。
change buffer包含的特性也叫作change buffering,包含insert buffering、delete buffering、purge buffering。
change buffer中數據類型和總量由參數innodb_change_buffering和innodb_chagne_buffer_max_size配置。查看change buffer中數據的信息,可以通過show engine innodb status查看。
change buffer在老版本中被稱作insert buffer。
1.監控change buffer
1
2
3
4
5
6
7
8
9
10
11
12
13
|
mysql> show engine innodb status\G
...
-------------------------------------
INSERT
BUFFER
AND
ADAPTIVE HASH
INDEX
-------------------------------------
Ibuf:
size
1,
free
list len 0, seg
size
2, 0 merges
merged operations:
insert
0,
delete
mark 0,
delete
0
discarded operations:
insert
0,
delete
mark 0,
delete
0
Hash
table
size
4425293, used cells 32, node heap has 1 buffer(s)
13577.57 hash searches/s, 202.47 non-hash searches/s
...
|
2.查看information_schema
information_schema.innodb_metrics提供了change buffer的統計信息名稱和說明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
mysql>
select
name
, comment
from
information_schema.innodb_metrics
where
name
like
'%ibuf%'
;
+
-----------------------------------------+-------------------------------------------------------------+
|
name
| comment |
+
-----------------------------------------+-------------------------------------------------------------+
| buffer_page_read_index_ibuf_leaf | Number
of
Insert
Buffer
Index
Leaf Pages
read
|
| buffer_page_read_index_ibuf_non_leaf | Number
of
Insert
Buffer
Index
Non-Leaf Pages
read
|
| buffer_page_read_ibuf_free_list | Number
of
Insert
Buffer
Free
List Pages
read
|
| buffer_page_read_ibuf_bitmap | Number
of
Insert
Buffer Bitmap Pages
read
|
| buffer_page_written_index_ibuf_leaf | Number
of
Insert
Buffer
Index
Leaf Pages written |
| buffer_page_written_index_ibuf_non_leaf | Number
of
Insert
Buffer
Index
Non-Leaf Pages written |
| buffer_page_written_ibuf_free_list | Number
of
Insert
Buffer
Free
List Pages written |
| buffer_page_written_ibuf_bitmap | Number
of
Insert
Buffer Bitmap Pages written |
| ibuf_merges_insert | Number
of
inserted records merged
by
change buffering |
| ibuf_merges_delete_mark | Number
of
deleted records merged
by
change buffering |
| ibuf_merges_delete | Number
of
purge records merged
by
change buffering |
| ibuf_merges_discard_insert | Number
of
insert
merged operations discarded |
| ibuf_merges_discard_delete_mark | Number
of
deleted merged operations discarded |
| ibuf_merges_discard_delete | Number
of
purge merged operations discarded |
| ibuf_merges | Number
of
change buffer merges |
| ibuf_size | Change buffer
size
in
pages |
| innodb_ibuf_merge_usec |
Time
(
in
microseconds) spent
to
process change buffer merge |
+
-----------------------------------------+-------------------------------------------------------------+
17
rows
in
set
(0.00 sec)
mysql>
|
information_schema.innodb_buffer_page提供了buffer pool中每個頁的元數據,包含change buffer 索引頁和change buffer位圖頁。
change buffer頁中page_type.ibuf_index表示change buffer索引頁;page_type.ibuf_bitmap表示change buffer位圖頁。
提醒:查看innodb_buffer_page會有很大的性能開銷。最好是在空閑時間或測試環境執行。
比如,可以查看change buffer的索引頁和位圖頁占據buffer pool的比例:
1
2
3
4
5
6
7
8
9
10
11
|
mysql>
select
(
select
count
(*)
from
information_schema.innodb_buffer_page
->
where
page_type
like
'ibuf%'
)
as
change_buffer_pages,
-> (
select
count
(*)
from
information_schema.innodb_buffer_page)
as
total_pages,
-> (
select
((change_buffer_pages/total_pages)*100))
->
as
change_buffer_page_percentage;
+
---------------------+-------------+-------------------------------+
| change_buffer_pages | total_pages | change_buffer_page_percentage |
+
---------------------+-------------+-------------------------------+
| 14 | 8192 | 0.1709 |
+
---------------------+-------------+-------------------------------+
1 row
in
set
(0.05 sec)
|
performance_schema還為高級性能監控提供了change buffer mutex的等待指令:
1
2
3
4
5
6
7
8
9
10
|
mysql>
SELECT
*
FROM
performance_schema.setup_instruments
->
WHERE
NAME
LIKE
'%wait/synch/mutex/innodb/ibuf%'
;
+
-------------------------------------------------------+---------+-------+
|
NAME
| ENABLED | TIMED |
+
-------------------------------------------------------+---------+-------+
| wait/synch/mutex/innodb/ibuf_bitmap_mutex |
NO
|
NO
|
| wait/synch/mutex/innodb/ibuf_mutex |
NO
|
NO
|
| wait/synch/mutex/innodb/ibuf_pessimistic_insert_mutex |
NO
|
NO
|
+
-------------------------------------------------------+---------+-------+
3
rows
in
set
(0.00 sec)
|
3.配置change buffer
可以通過參數innodb_change_buffering來控制是否啟用change buffer。
1
2
3
4
5
6
|
--all: 默認值。開啟buffer inserts、delete-marking operations、purges
--none: 不開啟change buffer
--inserts: 只是開啟buffer insert操作
--deletes: 只是開delete-marking操作
--changes: 開啟buffer insert操作和delete-marking操作
--purges: 對只是在后台執行的物理刪除操作開啟buffer功能
|
從5.6.2開始,參數innodb_change_buffer_max_size設置了change buffer可以占用buffer pool的百分比,默認是25,最大可以設置為50。
1
2
3
4
5
6
7
8
9
|
mysql> show variables
like
'innodb_change_buffer_max_size'
;
+
-------------------------------+-------+
| Variable_name | Value |
+
-------------------------------+-------+
| innodb_change_buffer_max_size | 25 |
+
-------------------------------+-------+
1 row
in
set
(0.00 sec)
mysql>
|