MySQL性能調優(7)複習補充

複習時自己產生的問題

log

緩衝池 Buffer Pool

首先,InnnoDB 的數據都是放在磁盤上的,InnoDB 操作數據有一個最小的邏輯單位,叫做頁(索引頁和數據頁)。我們對於數據的操作,不是每次都直接操作磁盤,因爲磁盤的速度太慢了。InnoDB 使用了一種緩衝池的技術,也就是把磁盤讀到的頁放到一 塊內存區域裏面。這個內存區域就叫 Buffer Pool,下一次讀取相同的頁,先判斷是不是在緩衝池裏面,如果是,就直接讀取,不用再 次訪問磁盤。 修改數據的時候,先修改緩衝池裏面的頁。內存的數據頁和磁盤數據不一致的時候, 我們把它叫做髒頁。InnoDB 裏面有專門的後臺線程把 Buffer Pool 的數據寫入到磁盤, 每隔一段時間就一次性地把多個修改寫入磁盤,這個動作就叫做刷髒。 Buffer Pool 是 InnoDB 裏面非常重要的一個結構,它的內部又分成幾塊區域。這裏 我們趁機到官網來認識一下 InnoDB 的內存結構和磁盤結構。

Buffer Pool
SHOW STATUS LIKE '%innodb_buffer_pool%';
官網鏈接
Buffer Pool 默認大小是 128M(134217728 字節),可以調整。
這個可以作爲優化,根據業務把緩衝池調大有利於更多的索引熱點數據查詢
https://dev.MySQL.com/doc/refman/5.7/en/server-status-variables.html
內存的緩衝池寫滿了InnoDB 用 LRU算法來管理緩衝池(鏈表實現,不是傳統的 LRU,分成了 young 和 old),經過淘汰的數據就是熱點數據。這個策略跟redis一樣。
Change Buffer 寫緩衝

如果這個數據頁不是唯一索引,不存在數據重複的情況,也就不需要從磁盤加載索 引頁判斷數據是不是重複(唯一性檢查)。這種情況下可以先把修改記錄在內存的緩衝 池中,從而提升更新語句(Insert、Delete、Update)的執行速度。把 Change Buffer 記錄到數據頁的操作叫做 merge。在訪問這個數據頁的時候,或者通過後臺線程、或者數據庫 shut down、 redo log 寫滿時觸發merge。

如果數據庫大部分索引都是非唯一索引,並且業務是寫多讀少,不會在寫數據後立 刻讀取,就可以使用 Change Buffer(寫緩衝)。寫多讀少的業務,調大這個值。 Change Buffer 佔 Buffer Pool 的比例,默認 25%

Adaptive Hash Index

索引應該是放在磁盤的。InnoDB本身不支持哈希索引,所有索引檢索都走B樹,Adaptive Hash index可以認爲是“索引的索引”。哈希索引就是採用一定的哈希算法,把鍵值換算成新的哈希值,檢索時不需要類似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法即可立刻定位到相應的位置,速度非常快。

(redo)Log Buffer

如果 Buffer Pool 裏面的髒頁還沒有刷入磁盤時,數據庫宕機或者重 啓,這些數據丟失。如果寫操作寫到一半,甚至可能會破壞數據文件導致數據庫不可用。爲了避免這個問題,InnoDB 把所有對頁面的修改操作專門寫入一個日誌文件,並且 在數據庫啓動時從這個文件進行恢復操作(實現 crash-safe)——用它來實現事務的持久性。這 種 日 志 和 磁 盤 配 合 的 整 個 過 程 , 其 實 就 是 MySQL 裏 的 WAL 技 術 (Write-Ahead Logging),它的關鍵點就是先寫日誌,再寫磁盤。

同樣是寫磁盤,爲什麼不直接寫到 db file 裏面去?爲什麼先寫日誌再寫磁盤?
我們先來了解一下隨機 I/O 和順序 I/O 的概念。
磁盤的最小組成單元是扇區,通常是 512 個字節。
操作系統和內存打交道,最小的單位是頁 Page。
操作系統和磁盤打交道,讀寫磁盤,最小的單位是塊 Block。
如果我們所需要的數據是隨機分散在不同頁的不同扇區中,那麼找到相應的數據需要等到磁臂旋轉到指定的頁,然後盤片尋找到對應的扇區,才能找到我們所需要的一塊數據,一次進行此過程直到找完所有數據,這個就是隨機 IO,讀取數據速度較慢。
假設我們已經找到了第一塊數據,並且其他所需的數據就在這一塊數據後邊,那麼就不需要重新尋址,可以依次拿到我們所需的數據,這個就叫順序 IO。
刷盤是隨機 I/O,而記錄日誌是順序 I/O,順序 I/O 效率更高。因此先把修改寫入日誌,可以延遲刷盤時機,進而提升系統吞吐。
當然 redo log 也不是每一次都直接寫入磁盤,在 Buffer Pool 裏面有一塊內存區域(Log Buffer)專門用來保存即將要寫入日誌文件的數據,默認 16M,它一樣可以節省磁盤 IO。
需要注意:redo log 的內容主要是用於崩潰恢復。磁盤的數據文件,數據來自 buffer pool。redo log寫入磁盤,不是寫入數據文件。那麼,Log Buffer 什麼時候寫入 log file?
在我們寫入數據到磁盤的時候,操作系統本身是有緩存的。flush 就是把操作系統緩衝區寫入到磁盤
log buffer 寫入磁盤的時機,由一個參數控制,默認是 1。
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
0(延遲寫)
log buffer 將每秒一次地寫入 log file 中,並且 log file 的 flush 操作同時進行。
該模式下,在事務提交的時候,不會主動觸發寫入磁盤的操作。
1(默認,實時寫,實時刷)
每次事務提交時 MySQL 都會把 log buffer 的數據寫入 log file,並且刷到磁盤
中去。
2(實時寫,延遲刷)
每次事務提交時 MySQL 都會把 log buffer 的數據寫入 log file。但是 flush 操
作並不會同時進行。該模式下,MySQL 會每秒執行一次 flush 操作

問題:Redo log 和db文件都是在磁盤上的,爲什麼寫redo log 是順序io 寫db文件就是隨機IO呢?

buffer pool中有很多數據等待刷髒的時候,寫入redo log是順序寫入的。
而這些數據在磁盤中的位置不是連續的,每次都要重新尋址,所以是隨機I/O。
數據寫入到redo log裏面就有了保障,刷盤就不需要那麼頻繁了,提升了系統的吞吐量。MySQL很多地方都利用的這一點

雙寫緩衝問題

InnoDB 的頁和操作系統的頁大小不一致,InnoDB 頁大小一般爲 16K,操作系統頁 大小爲 4K,InnoDB 的頁寫入到磁盤時,一個頁需要分 4 次寫。如果存儲引擎正在寫入頁的數據到磁盤時發生了宕機,可能出現頁只寫了一部分的 情況,比如只寫了 4K,就宕機了,這種情況叫做部分寫失效(partial page write),可能會導致數據丟失,如果這個頁本身已經損壞了,用redo log來做崩潰恢復是沒有意義的。所以在對於應用 redo log 之前,需要一個頁的副本。如果出現了寫入失效,就用頁的副本來還原這個頁,然後再應用 redo log。這個頁的副本就是 double write,InnoDB 的雙寫技術。通過它實現了數據頁的可靠性。

跟redo log一樣,double write 由兩部分組成,一部分是內存的 double write,一個部分是磁盤上的 double write。因爲 double write 是順序寫入的,不會帶來很大的開銷
Binlog

binlog 以事件的形式記錄了所有的 DDL 和 DML 語句(因爲它記錄的是操作而不是 數據值,屬於邏輯日誌),可以用來做主從複製和數據恢復。 跟 redo log 不一樣,它的文件內容是可以追加的,沒有固定大小限制。 在開啓了 binlog 功能的情況下,我們可以把 binlog 導出成 SQL 語句,把所有的操 作重放一遍,來實現數據的恢復。 binlog 的另一個功能就是用來實現主從複製,它的原理就是從服務器讀取主服務器 的 binlog,然後執行一遍。

例如一條語句:update teacher set name='盆魚宴' where id=1;
1、先查詢到這條數據,如果有緩存,也會用到緩存。
2、把 name 改成盆魚宴,然後調用引擎的 API 接口,寫入這一行數據到內存,同時記錄 redo log。這時 redo log 進入 prepare 狀態,然後告訴執行器,執行完成了,可以隨時提交。
3、執行器收到通知後記錄 binlog,然後調用存儲引擎接口,設置 redo log爲 commit狀態。
4、更新完成。
  1. 先記錄到內存,再寫日誌文件。
  2. 記錄 redo log 分爲兩個階段。
  3. 存儲引擎和 Server 記錄不同的日誌。
  4. 先記錄 redo,再記錄 binlog。

index

頁分裂

InnoDB在葉子上存儲數據。一個節點就是一頁。一個頁可以存儲多行數據,按照主鍵的順序存儲。當數據是順序插入的時候,一頁寫滿了,就申請一個新的頁。如果是隨機插入,在指定位置的頁已經寫滿了(或者到達了分裂閾值)的時候,就會發生頁結構的調整(即B+Tree的節點的分裂)

頁的內部原理

頁可以空或者填充滿(100%),行記錄會按照主鍵順序來排列。這裏有個重要的屬性:MERGE_THRESHOLD。該參數的默認值是50%頁的大小,它在InnoDB的合併操作中扮演了很重要的角色

當你插入數據時,如果數據(大小)能夠放的進頁中的話,那他們是按順序將頁填滿的。根據B樹的特性,它可以自頂向下遍歷,但也可以在各葉子節點水平遍歷。因爲每個葉子節點都有着一個指向包含下一條(順序)記錄的頁的指針。那麼如果我突然執行刪除,頁會出現什麼情況?

頁合併

當你刪了一行記錄時,實際上記錄並沒有被物理刪除,記錄被標記(flaged)爲刪除並且它的空間變得允許被其他記錄聲明使用。當頁中刪除的記錄達到MERGE_THRESHOLD(默認頁體積的50%),InnoDB會開始尋找最靠近的頁(前或後)看看是否可以將兩個頁合併以優化空間使用。

頁分裂

當前頁有空間但是容納不下我要插入的數據時。下一頁又是滿的無法插入數據時。

1. 創建新頁
2. 判斷當前頁(頁#10)可以從哪裏進行分裂(記錄行層面)
3. 移動記錄行
4. 重新定義頁之間的關係
頁分裂會發生在插入或更新,並且造成頁的錯位(dislocation,落入不同的區)論id設計的重要性

感謝這篇文章

索引用到文件排序 Using filesort
Using filresort代表:不能使用索引來排序,用到了額外的排序。
例如:查詢用到了a字段上的idx_a,但是後面有order by b。
一些優化方式:
1.建立聯合索引idx(a,b)
2.不使用MySQL的排序,改成在代碼中排序
爲什麼要固定Page大小,而不是需要多少數據,讀取多少數據(按需讀取)爲什麼16k
設置頁的大小與磁盤的預讀取特性有關係。局部性原理認爲:當一個數據被用到時,其附近的數據也通常會馬上被使用。所以順序讀取附近的數據,可以提升I/O效率。每次都至少讀取一頁
默認的16KB大小,在涉及表掃描和批量更新的業務場景中更實用,效率已經很高了
官網解釋
https://dev.MySQL.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_page_size
表沒主鍵索引問題
如果我們定義了主鍵(PRIMARY KEY),那麼 InnoDB 會選擇主鍵作爲聚集索引。 
如果沒有顯式定義主鍵,則 InnoDB 會選擇第一個不包含有 NULL 值的唯一索引作爲主鍵索引、
如果也沒有這樣的唯一索引,則 InnoDB 會選擇內置 6 字節長的 ROWID 作爲隱 藏的聚集索引,它會隨着行記錄的寫入而主鍵遞增。
輔助索引什麼情況下查詢需要回表,什麼情況下不需要回表
例如索引是index(a,b,c),select 索引裏面的列
(包括select a,select b,select c,select a,b select a,c select b,c,select a,b,c),
都不需要到主鍵索引的葉子節點獲取完整數據,此時不需要回表。

事務與鎖

共享鎖 有什麼意義呢
舉個例子,order_info 和 order_detail 有邏輯的主外鍵關係。
在操作order_detail的時候,不希望order_info被其他的事務修改,可以用共享鎖,阻塞其他的事務修改
innodb有了MVCC爲什麼還需要LBCC
快照讀(普通的select)用MVCC保證讀一致性。
加鎖的讀和增刪改,用lock保證讀一致性
MySQL ACID怎麼保證
原子性(Atomicity,或稱不可分割性) 通過undo log 實現
一致性(Consistency)通過AID特性與應用保證一致性
隔離性(Isolation)MVCC
持久性(Durability)通過redo log實現

優化

連接池大小應該設置多大
我們不妨想一下,爲啥 Nginx 內部僅僅使用了 4 個線程,其性能就大大超越了 100 個進程的 Apache HTTPD 呢?
在一核 CPU 的機器上,順序執行A和B永遠比通過時間分片切換“同時”執行A和B要快,其中原因,學過操作系統這門課程的童鞋應該很清楚。一旦線程的數量超過了 CPU 核心的數量,再增加線程數系統就只會更慢,而不是更快,因爲這裏涉及到上下文切換耗費的額外的性能。
在加上現在的磁盤又基本是SSD,無需尋址和沒有旋迴耗時的確意味着更少的阻塞所以更少的線程(更接近於CPU核心數)會發揮出更高的性能,只有當阻塞密集時,更多的線程數才能發揮出更好的性能。

連接數 = ((核心數 * 2) + 有效磁盤數)

當然,這取決於線上環境,開發還是設置大一點,因爲大家都在聯調測試。

Count問題
MyISAM之所以可以把表中的總行數記錄下來供COUNT(*)查詢使用,那是因爲MyISAM數據庫是表級鎖,不會有併發的數據庫行數修改,所以查詢得到的行數是準確的。
但是,對於InnoDB來說,就不能做這種緩存操作了,因爲InnoDB支持事務,其中大部分操作都是行級鎖,所以可能表的行數可能會被併發修改,那麼緩存記錄下來的總行數就不準確了。
我們知道,InnoDB中索引分爲聚簇索引(主鍵索引)和非聚簇索引(非主鍵索引),聚簇索引的葉子節點中保存的是整行記錄,而非聚簇索引的葉子節點中保存的是該行記錄的主鍵的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL會優先選擇最小的非聚簇索引來掃表。所以,當我們建表的時候,除了主鍵索引以外,創建一個非主鍵索引還是有必要的。
COUNT(*)和COUNT(1)和count(字段)區別

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference. 官方發話

畫重點:same way,no performance difference所以,對於COUNT(1)和COUNT(*),MySQL的優化是完全一樣的,根本不存在誰比誰快!

建議使用COUNT(*)!因爲這個是SQL92定義的標準統計行數的語法。

COUNT(字段)多了一個步驟就是判斷所查詢的字段是否爲NULL所以他的性能要比COUNT(*)慢。

存儲引擎

InnoDB和MyISAM區別
  1. InnoDB 支持事務,MyISAM 不支持事務。這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一
  2. InnoDB 支持外鍵,而 MyISAM 不支持。對一個包含外鍵的 InnoDB 錶轉爲 MYISAM 會失敗
  3. InnoDB 是聚集索引數據跟索引存放在一起,MyISAM 是非聚集索引數據和索引分開。
  4. InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。
  5. 由於索引分開的MyISAM 在內存中存儲了row_count 值的 meta 信息可以直接獲取到總行數
底層使用B+樹不用B樹
  1. AVL(平衡二叉樹)樹解決了索引頻繁的修改,和查詢效率不高的問題。
  2. B樹相對AVL樹,每個節點存儲的數據更多,路數更多,樹的深度減少,減少I/O次數,提升效率。
  3. B+樹相對B樹,效率更穩定,因爲數據存在葉子節點;排序能力更強,因爲葉子節點有下一個數據區的指針;讀寫能力更強,因爲根節點和枝節點不用存數據,所以可以保存更多的關鍵字。掃庫掃表能力更強,因爲數據都存在葉子節點,而且葉子節點都有下一個數據區的指針,所以遍歷起來很方便。
B+樹數據存放量問題
假設我們的樹深度爲2 我們id用bigint數據類型佔8字節 我們一條數據1k大小
指針在InnoDB中佔6字節 一個頁爲16k=16384字節 
我們一頁就可以存放16384/14=1170個指向記錄 注意!這是根節點
每個指向記錄下面存(16k/1k)*1170=18720記錄 
如果深度爲3那麼就是1170*1170*16=21902400條記錄
所以說我們2000萬的數據如果通過id搜索也就3次io。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章