一小時總結 – MySql 知識(一)
數據庫知識是開發和麪試必須掌握的知識,只有瞭解數據庫的脾性才能更好的使用它。
在衆多數據庫軟件裏面,Oracle 的性能最強悍,目前基本上還沒有 Oracle 一體機跑不動的應用(如果有隻能說明你的體量太大或者使用太爛)。MySql 作爲免費開源的代表基本上是大中小企業的首選。也是面試和應用中應該掌握的重點。
以下內容爲默記整理內容,如存在表述錯誤表述不準確歡迎指正。
要點:
- 原理性知識:
- 整體:MySql 的體系結構;
- 線程模型:線程連接池;
- 內存模式:緩衝區;
- 網絡模型:reactor 模型;
- 文件模型:B+樹、binlog 及重做日誌
- 高可用:主從複製、binlog 及重做日誌;
- 擴展原理:MySql 引擎對比;
- 效能原理:MySql 的索引結構和存儲;
- 實現原理:MySql InnoDB 如何實現事務;
- InnoDB MVVC 的原理和實現;
- InnoDB 鎖相關知識;
- 整體:MySql 的體系結構;
- 應用性知識:
- explain 的用法和關注點;
- 慢查詢的查詢思路;
- sql 優化原則;
- 如何選擇合適的存儲引擎;
原理性知識
整體:MySql 的體系結構
MySql 整體可分爲三層:
- 連接層:最上層,實現連接管理;
- ??中間層??:MySql 本身實現的查詢緩存、sql 解析、sql 優化器,中間層調用底層存儲引擎層的 Api 實現 sql 的執行和優化;
- 存儲引擎層:最底層,可以以插件形式實現 MySql 的文件存儲和數據查詢;
線程模型:線程池
MySql 採用多線程實現,其內部運行了多個線程,採用統一線程池管理,每個連接的查詢發送到 mysql 後,有一個獨立線程負責此次 sql 的執行。
內存模型:緩衝區
MySql 採用內存緩衝區的方式提升應用效率,爲了降低磁盤 IO 次數,MySql 儘量把熱點數據緩存到內存中。
- 寫緩衝:當有數據發生修改後,MySql 採用寫緩衝的方式先操作內存數據,待合適時機再將數據修改寫入到物理磁盤;
- IO 合併:MySql 中的很多 IO 爲邏輯 IO,邏輯 IO 可能操作的是緩存數據,待合適時機多個邏輯 IO 會被合併爲一個物理 IO 進行一次性操作。
網絡模型:Reactor 模型
MySql 的網絡連接和驅動採用 Reactor 模型管理連接,僅當數據準備充分後再分配線程。
文件模型:B+樹、binlog 及重做日誌
爲了充分利用磁盤特性,尤其是 HDD 磁盤的磁頭移動特性,MySql 採用:
- 使用
矮胖
的 B+ 樹作爲索引的存儲和實現結構,相比 AVL 樹 B+ 樹有更低的樹高度,能有效的減少磁盤 IO 次數,而相對 B 樹,B+ 樹的搜索效率更穩定,葉子節點的鏈表指針讓範圍查詢變得更容易和高效,更能充分利用磁盤連續讀和操作系統 page cache 特性。 - binlog 及重做日誌:爲了儘量減少磁盤 IO,MySql 將數據寫入內存緩衝帶來的風險是機器掉電帶來的數據丟失風險,因此 MySql 設計了 binlog 和重做日誌。binlog 和重做日誌在設計上採用單文件追加的方式具有更高的刷盤效率。
高可用:主從複製、binlog 及重做日誌
MySql 的高可用提現在兩個方面:
- 單機掉電的情況下:可保證的數據一致性;
- 提供集羣容災策略:主從複製策略,並提供主從數據一致性保證;
單機掉電:
- 在內存模型中,MySql 爲了提升操作效率大部分操作的是內存緩衝數據,爲了保證掉電後緩衝數據丟失帶來的數據丟失風險,mysql 建立了 binlog 和重做日誌機制(InnoDB 機制);
- binlog 機制:binlog 記錄了所有的 sql 操作(select 不記錄),利用 binlong 可實現主從複製和宕機重啓後的數據恢復。
- 重做日誌:innodb 實現的日誌,分爲 redo log 和 undo log,redo log ,redo log 記錄了尚未刷新到磁盤的數據變化,undo log 記錄了事務執行過程產生的事務日誌可實現數據回滾。
擴展原理:MySql 引擎對比
MySql 的存儲引擎機制讓 MySql 本身不 care 數據如何被存儲和索引。存儲引擎機制讓 MySql 可適用多種適用場景:
- ISAM 和 MyISAM:早期 MySql 官方默認的存儲引擎,最早引擎叫 ISAM 後面改名叫 MyISAM,ISAM 是一種算法。MyISAM 引擎的實現簡單不支持事務,僅支持表鎖但是其數據存儲的空間效率高。適合
- InnoDB:MySql 5.6 後官方默認的引擎,InnoDB 引擎支持事務並擴展實現了行級鎖。適合絕大部分 OLTP 應用(增刪改查比較均衡的場景)
- 其他引擎:NDB 等,有些將數據以列方式組織存儲,有些將數據全部放到內存中提升效率各有自己的目標應用場景。
效能原理:MySql 的索引結構和存儲
基礎原理
MySql 的文件分爲兩類:
- 表定義文件:這類文件 MySql 本身實現了相關文件的存儲;
- 表數據文件:這類文件 MySql 交給存儲引擎去實現相關數據的存儲;
索引文件基本上由存儲引擎來實現,所有存儲引擎基本上使用 B+ 樹
MyISAM 的實現
MyISAM 的存儲和索引實現比較簡單,
- 表數據:表數據按插入行順序存儲在表文件中;
- 索引:MyISAM 的索引使用 B+ 樹實現,葉子節點存儲了行的物理文件指針,通過此指針可以定位到具體行數據。
InnoDB 的實現
InnoDB 的表被稱爲索引組織表,表數據和索引數據存放在一起;
- 表數據:表數據按行組織,作爲主鍵索引的葉子節點存放,具體數據表還分爲:區、頁、端 等;
- 索引:採用 B+ 樹實現,主鍵索引的葉子節點存儲了行數據,非主鍵索引的葉子節點存儲了主鍵值,因此非主鍵索引查詢非索引值要進行回表操作。
實現原理:MySql InnoDB 如何實現事務
InnoDB 引擎支持 4 級事務,MyISAM 是不支持事務的。InnoDB 採用下面的技術實現了事務的 ACID 特性。
- 重做日誌:採用 redo log 和 undo log 的方式保證事務的一致性和持久性。
- MVVC 和行鎖:實現了事務的隔離性;
爲了提升事務的併發效率,InnoDB 採用 MVVC 技術提供了 非鎖定讀一致性寫
的特性。
MVVC 技術
- MVVC:多版本併發控制技術,簡單理解是某行併發操作數據在同一個時刻可能存在多個快照版本,根據事務的隔離級別,InnoDB 會確定此事務可見的數據快照版本。
- 實現:同時真的維護多個快照版本數據是難以實現的,因此 InnoDB 採用在每行上添加三個字段的方式來實現:創建號、刪除號、undo 指針。
- 快照數據的確定:每個事務都會被分配一個事務 id,事務 id 是嚴格自增的,在可重複讀級別下,某個行在某個事務中是否可見,是通過比較當前事務 id 和行數據的創建號、刪除號來決定的,具體確定策略;
- 行可見:行的刪除號 == null && 行的創建號 <= 當前事務號;
- 新增行:行的創建號 = 當前事務號 && 行的刪除號 = null;
- 刪除行:行的刪除號 = 當前事務號;
- 歷史版本:如果某個事務併發的修改了其中一行,那麼此行的歷史版本通過 undo 指針可以從 undo 日誌中找到。
鎖
- 行鎖:鎖定某一行,加了行鎖後,其他併發事務會被阻塞;
- 間隙鎖:鎖定某一個範圍,加了範圍鎖後,其他併發事務在此範圍內操作數據都會被阻塞,間隙鎖是一個開區間鎖;
- next key 鎖:行鎖 + 間隙鎖,是一個左閉右開的鎖;
- InnoDB 在讀已提交的隔離級別下:加行鎖;
- InnoDB 在可重複讀的隔離級別下:加 next key lock;
應用性知識
explain 的用法和關注點;
explain 用來分析 sql 的查詢計劃,本身 explain 用法比較複雜,實際應用中要關注的點很多,但是很多點要關聯起來看,一般來說,某個 SQL 的效率可以通過下面幾個點來關注:
- keys:是否走了索引;
- extra:是否有 using file sorted 和 using temporary;
- rows:查詢出來的數據是否過大;
慢查詢的查詢思路
慢查詢並不一定等於慢 sql,慢查詢先要定位是否是慢 sql:
- 是否慢 sql:查看 mysql 的慢 sql 日誌,如果慢 sql 的閾值不理想可調整閾值後再觀察;
- 如果慢 sql 日誌出現了記錄,則分析相關 sql,如果沒有則問題查詢方向要改爲關注 mysql 連接層和網絡狀況。
mysql 連接層和網絡狀態
- 先確認 mysql 的壓力:是否連接過載很大,導致大部分應用連接沒有及時獲得 thread 陷入了等待,結合 top 等命令查詢;
- 排除 mysql 的壓力,確認網絡狀況是否良好,可以使用 tcpdump 抓包,確定是否有 tcp 重傳或者網絡抖動帶來的網絡大面積延遲。通過 trace 等命令確定某個網關路由是否有問題,通過 ping 、telnet 來確認業務主機和 mysql 的網卡是否有問題。
慢 sql 確定
慢 sql 的主要確認方式是通過 explain 來分析 sql 效率,並結合 sql 優化原則來優化自己的 sql 達到理想執行效率。
sql 優化原則
- 儘量做到索引覆蓋:做到索引覆蓋則可以避免 mysql 的回表操作;
- 減少不必要的字段查詢:可以縮小數據包增強網絡傳輸效率;
- 查詢要走索引:走索引則可以快速過濾不必要的數據,減少全表掃描,減少數據的 IO 次數,具體做法:
- 避免索引字段爲 null 的查詢;
- 做到最左前綴匹配,儘量去除 like ‘%a%’ 的查詢;
- 用 exist 替代 in 操作,尤其是 in 的值過多的情況;
- 使用預編譯語句:預編譯語句不僅能避免 sql 注入還可以加快查詢效率,避免每次的 sql 解析,還能充分幫助數據庫命中查詢緩存。