複習時自己產生的問題
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、更新完成。
- 先記錄到內存,再寫日誌文件。
- 記錄 redo log 分爲兩個階段。
- 存儲引擎和 Server 記錄不同的日誌。
- 先記錄 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區別
- InnoDB 支持事務,MyISAM 不支持事務。這是 MySQL 將默認存儲引擎從 MyISAM 變成 InnoDB 的重要原因之一
- InnoDB 支持外鍵,而 MyISAM 不支持。對一個包含外鍵的 InnoDB 錶轉爲 MYISAM 會失敗
- InnoDB 是聚集索引數據跟索引存放在一起,MyISAM 是非聚集索引數據和索引分開。
- InnoDB 最小的鎖粒度是行鎖,MyISAM 最小的鎖粒度是表鎖。
- 由於索引分開的MyISAM 在內存中存儲了row_count 值的 meta 信息可以直接獲取到總行數
底層使用B+樹不用B樹
- AVL(平衡二叉樹)樹解決了索引頻繁的修改,和查詢效率不高的問題。
- B樹相對AVL樹,每個節點存儲的數據更多,路數更多,樹的深度減少,減少I/O次數,提升效率。
- 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。