1、存儲結構
- 記錄是按照行來存儲的,可是數據庫的讀取並不以行爲單位,不然一次讀取(也就是一次 I/O 操做)只能處理一行數據,效率會很是低。
- 在數據庫中,不論讀一行,仍是讀多行,都是將這些行所在的頁進行加載。數據庫管理存儲空間的基本單位是頁(Page)。
表空間 | Tablespace |
|
段 | Segment |
|
區 | Extent |
|
頁 | Page |
|
行記錄 | Row |
|
2、數據頁內的結構
- 按類型劃分:常見的有數據頁(保存 B+ 樹節點)、系統頁、Undo 頁和事務數據頁等。數據頁是咱們最常使用的頁。
- 表頁的大小限定了錶行的最大長度,不一樣 DBMS 的表頁大小不一樣
- 好比在 MySQL 的 InnoDB 存儲引擎中,默認頁的大小是 16KB。
show variables like '%innodb_page_size%';
- 數據庫 I/O 操做的最小單位是頁,與數據庫相關的內容都會存儲在頁結構裏
- 數據頁包括七個部分,分別是文件頭(File Header)、頁頭(Page Header)、最大最小記錄(Infimum+supremum)、用戶記錄(User Records)、空閒空間(Free Space)、頁目錄(Page Directory)和文件尾(File Tailer)。
第一部分,文件通用部分,文件頭和文件尾
- 文件頭和文件尾,相似集裝箱,將頁的內容進行封裝,經過文件頭和文件尾校驗的方式來確保頁的傳輸是完整的
- 文件頭中有兩個字段,分別是 FIL_PAGE_PREV 和 FIL_PAGE_NEXT,它們的做用至關於指針,分別指向上一個數據頁和下一個數據頁,一個雙向的鏈表
- 數據頁之間不須要是物理上的連續,而是邏輯上的連續
- 文件尾的校驗方式就是採用 Hash 算法進行校驗
- 經過文件尾的校驗和(checksum 值)與文件頭的校驗和作比對,若是兩個值不相等則證實頁的傳輸有問題,須要從新進行傳輸,不然認爲頁的傳輸已經完成
第二部分,記錄部分
- 頁的主要做用是存儲記錄,「最小和最大記錄」和「用戶記錄」部分佔了頁結構的主要空間
- 另外空閒空間是個靈活的部分,有新的記錄插入時,會從空閒空間中進行分配用於存儲新記錄。
第三部分,索引部分
- 重點指的是頁目錄,起到了記錄的索引做用
- 在頁中,記錄是以單向鏈表的形式進行存儲的
- 單向鏈表的特色就是插入、刪除很是方便,可是檢索效率不高,最差的狀況下須要遍歷鏈表上的全部節點才能完成檢索
- 在頁目錄中提供了二分查找的方式,用來提升記錄的檢索效率
- 將全部的記錄分紅幾個組,包括最小記錄和最大記錄,但不包括標記爲「已刪除」的記錄
- 第 1 組,最小記錄所在的分組只有 1 個記錄;最後一組(最大記錄所在的分組)會有 1-8 條記錄;其他的組記錄數量在 4-8 條之間。除了第 1 組(最小記錄所在組)之外,其他組的記錄數會盡可能平分。
- 在每一個組中最後一條記錄的頭信息中會存儲該組一共有多少條記錄,做爲 n_owned 字段。
- 頁目錄用來存儲每組最後一條記錄的地址偏移量,這些地址偏移量會按照前後順序存儲起來,每組的地址偏移量也被稱之爲槽(slot),每一個槽至關於指針指向了不一樣組的最後一個記錄
上面的圖示進行舉例算法
- 5 個槽的編號分別爲 0,1,2,3,4,查找主鍵爲 9 的用戶記錄
- 初始化查找的槽的下限編號,設置爲 low=0,設置查找的槽的上限編號 high=4
- 採用二分查找法進行查找,首先找到槽的中間位置 p=(low+high)/2=(0+4)/2=2
- 取編號爲 2 的槽對應的分組記錄中最大的記錄,關鍵字爲 8
- 由於 9 大於 8,因此會在槽編號爲 (p,high]的範圍進行查找
- 接着從新計算中間位置 p’=(p+high)/2=(2+4)/2=3
- 查找編號爲 3 的槽對應的分組記錄中最大的記錄,關鍵字爲 12
- 由於 9 小於 12,因此應該在槽 3 中進行查找
- 遍歷槽 3 中的全部記錄,找到關鍵字爲 9 的記錄,取出該條記錄的信息,即爲要查找的內容
頁目錄存儲的是槽,槽至關於分組記錄的索引,經過槽(二分查找)查找記錄。sql
3、從數據頁的角度看 B+ 樹是如何進行查詢的
- 葉子節點,B+ 樹最底層的節點,節點的高度爲 0,存儲行記錄
- 非葉子節點,節點的高度大於 0,存儲索引鍵和頁面指針,不存儲行記錄自己
- 一棵 B+ 樹中,每一個節點都是一個頁,每次新建節點的時候,就會申請一個頁空間
- 同一層上的節點之間,經過頁的結構構成一個雙向的鏈表(頁文件頭中的兩個指針字段)
- 非葉子節點,包括了多個索引行,每一個索引行裏存儲索引鍵和指向下一層頁面的頁面指針
- 最後是葉子節點,它存儲了關鍵字和行記錄,在節點內部(也就是頁結構的內部)記錄之間是一個單向的鏈表,可是對記錄進行查找,則能夠經過頁目錄採用二分查找的方式來進行
經過 B+ 樹的索引查詢行記錄數據庫
- 首先是從 B+ 樹的根開始,逐層檢索,直到找到葉子節點,也就是找到對應的數據頁爲止
- 將數據頁加載到內存中
- 頁目錄中的槽(slot)採用二分查找的方式先找到一個粗略的記錄分組
- 而後再在分組中經過鏈表遍歷的方式查找記錄。
普通索引和惟一索引,查詢效率比較緩存
- 惟一索引就是在普通索引上增長了約束性,關鍵字惟一,找到了關鍵字就中止檢索
- 普通索引,可能會存在用戶記錄中的關鍵字相同的狀況
- 根據頁結構的原理,當咱們讀取一條記錄的時候,不是單獨將這條記錄從磁盤中讀出去,而是將這個記錄所在的頁加載到內存中進行讀取
普通索引的字段上進行查找也就是比惟一索引在內存中多幾回「判斷下一條記錄」的操做,檢索效率上基本上沒有差異服務器
4、緩衝池
- 磁盤 I/O 須要消耗的時間不少,在內存中進行操做效率則會高不少
- 爲了提升性能,DBMS 會申請佔用內存來做爲數據緩衝池,讓磁盤活動最小化,從而減小與磁盤直接進行 I/O 的時間,提高查詢性能
- 若是索引的數據在緩衝池裏,訪問的成本就會下降不少
- 緩衝池管理器會盡可能將常用的數據保存起來,在數據庫進行頁面讀操做的時候,首先會判斷該頁面是否在緩衝池中
- 若是存在就直接讀取,若是不存在,就會經過內存或磁盤將頁面存放到緩衝池中再進行讀取
- 對數據庫中的記錄進行修改的時候,首先會修改緩衝池中頁裏面的記錄信息
- 數據庫會以必定的頻率刷新到磁盤上
- 並非每次發生更新操做,都會馬上進行磁盤迴寫
- 緩衝池會採用一種叫作 checkpoint 的機制將數據回寫到磁盤上,提高數據庫的總體性能
- 當緩衝池不夠用時,須要釋放掉一些不經常使用的頁
- 能夠採用強行採用 checkpoint 的方式,將不經常使用的髒頁回寫到磁盤上
- 再從緩衝池中將這些頁釋放掉【髒頁(dirty page)指的是緩衝池中被修改過的頁,與磁盤上的數據頁不一致】
- MyISAM 存儲引擎,只緩存索引,不緩存數據,對應的鍵緩存參數爲 key_buffer_size。
- InnoDB 存儲引擎,能夠經過查看 innodb_buffer_pool_size 變量來查看緩衝池的大小。
show variables like 'innodb_buffer_pool_size'; -- 查看 set global innodb_buffer_pool_size = xxxx; -- 設置 show variables like 'innodb_buffer_pool_instances'; -- 查看緩衝池的個數若是想要開啓多個緩衝池性能
- 首先須要將innodb_buffer_pool_size參數設置爲大於等於 1GB,這時innodb_buffer_pool_instances纔會大於 1
- 能夠在 MySQL 的配置文件中對innodb_buffer_pool_size進行設置,大於等於 1GB
- 而後再針對innodb_buffer_pool_instances參數進行修改
緩衝池三種讀取數據的方式
1. 內存讀取,若是該數據存在於內存中,基本上執行時間在 1ms 左右,效率仍是很高的。spa
2. 隨機讀取指針
若是數據沒有在內存中,就須要在磁盤上對該頁進行查找,總體時間預估在 10ms 左右code
- 6ms 是磁盤的實際繁忙時間(包括了尋道和半圈旋轉時間)
- 3ms 是對可能發生的排隊時間的估計值
- 還有 1ms 的傳輸時間,將頁從磁盤服務器緩衝區傳輸到數據庫緩衝區中
10ms 看起來很快,但實際上對於數據庫來講消耗的時間已經很是長,由於只是一個頁的讀取時間對象
3. 順序讀取
一種批量讀取的方式
- 咱們請求的數據在磁盤上每每都是相鄰存儲的,順序讀取能夠幫咱們批量讀取頁面
- 一次性加載到緩衝池中就不須要再對其餘頁面單獨進行磁盤 I/O 操做了
- 若是一個磁盤的吞吐量是 40MB/S,那麼對於一個 16KB 大小的頁來講,一次能夠順序讀取 2560(40MB/16KB)個頁,至關於一個頁的讀取時間爲 0.4ms
- 採用批量讀取的方式,即便是從磁盤上進行讀取,效率也比從內存中只單獨讀取一個頁的效率要高
SQL 語句的查詢成本
SHOW STATUS LIKE 'last_query_cost';
- 一條 SQL 查詢語句在執行前須要肯定查詢計劃,若是存在多種查詢計劃的話,MySQL 會計算每一個查詢計劃所須要的成本,從中選擇成本最小的一個做爲最終執行的查詢計劃
- 在執行完這條 SQL 語句以後,經過查看當前會話中的 last_query_cost 變量值來獲得當前查詢的成本
- 這個查詢成本對應的是 SQL 語句所須要讀取的頁的數量
- 位置決定效率。若是頁就在數據庫緩衝池中,那麼效率是最高的,不然還須要從內存或者磁盤中進行讀取,固然針對單個頁的讀取來講,若是頁存在於內存中,會比在磁盤中讀取效率高不少
- 批量決定效率。若是咱們從磁盤中對單一頁進行隨機讀,那麼效率是很低的(差很少 10ms),而採用順序讀取的方式,批量對頁進行讀取,平均一頁的讀取效率就會提高不少,甚至要快於單個頁面在內存中的隨機讀取
考慮數據存放的位置,若是是常用的數據就要儘可能放到緩衝池中,其次能夠充分利用磁盤的吞吐能力,一次性批量讀取數據,這樣單個頁的讀取效率也就獲得了提高。
查詢緩存
- 提早把查詢結果緩存起來,這樣下次就不須要執行能夠直接拿到結果
- 在MySQL中的查詢緩存,不是緩存查詢計劃,而是查詢及對應的查詢結果
- 查詢匹配的魯棒性大大下降,只有相同的查詢操做纔會命中查詢緩存
緩存池與查詢緩存,異同:(緩衝池不是查詢緩存)
共同的特色:都是經過緩存的機制來提高效率。
差別:
緩衝池 | 服務於數據庫總體的IO操做,經過創建緩衝池機制來彌補存儲引擎的磁盤文件與內存訪問之間的效率鴻溝,同時緩衝池會採用「預讀」的機器提早加載一些立刻會用到的數據,以提高總體的數據庫性能 |
查詢緩存 | 服務於SQL查詢和查詢結果集的,由於命中條件苛刻,並且只要當數據表發生了變化,查詢緩存就會失效,命中率低 |