MySQL 内核原理分析(一)
生活随笔
收集整理的這篇文章主要介紹了
MySQL 内核原理分析(一)
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
- 學(xué)一個(gè)技術(shù),我們先要 跳出來,看整體,先要在腦中有一個(gè)這個(gè)技術(shù)的全貌。然后再 鉆進(jìn)去,看本質(zhì),深入的研究細(xì)節(jié)。這樣方便我們建立一個(gè)立體的知識(shí)網(wǎng)絡(luò)。不然單學(xué)多個(gè)知識(shí)點(diǎn),是串不起來的。不容易記住,理解也不會(huì)深刻。
- 所以,我們先把 MySQL 拆解一下,看看內(nèi)部有哪些組件,我們 Java系統(tǒng)執(zhí)行一條SQL,MySQL的內(nèi)部是如何運(yùn)作,給我們返回結(jié)果的。
- 我們先從我們?cè)L問數(shù)據(jù)庫(kù)說起
- 我們想要查詢數(shù)據(jù)庫(kù),首先得建立網(wǎng)絡(luò)連接
- MySQL 驅(qū)動(dòng)負(fù)責(zé)建立網(wǎng)絡(luò)連接,然后請(qǐng)求 MySQL 數(shù)據(jù)庫(kù)
- 其實(shí)就是創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)連接
- Java系統(tǒng)的 數(shù)據(jù)庫(kù)連接池
- 如果我們的系統(tǒng)所有線程訪問數(shù)據(jù)庫(kù)時(shí),都使用一個(gè)連接會(huì)怎樣
- 所有線程搶奪一個(gè)連接,沒有連接 就操作不了數(shù)據(jù)庫(kù),效率極低,因?yàn)樾枰竺娴木€程需要等待前面的線程處理完才行
- 所有線程搶奪一個(gè)連接,沒有連接 就操作不了數(shù)據(jù)庫(kù),效率極低,因?yàn)樾枰竺娴木€程需要等待前面的線程處理完才行
- 如果我們的系統(tǒng)所有線程訪問數(shù)據(jù)庫(kù)時(shí),都使用一個(gè)連接會(huì)怎樣
- 我們的系統(tǒng)如果每個(gè)線程訪問數(shù)據(jù)庫(kù)時(shí),都創(chuàng)建一個(gè)連接,然后銷毀,會(huì)怎樣
- 創(chuàng)建連接需要網(wǎng)絡(luò)通信,網(wǎng)絡(luò)通信是很耗時(shí)的
- 好不容易創(chuàng)建了連接,查詢完就給銷毀了,那效率肯定低
- 所以,我們要使用數(shù)據(jù)庫(kù)連接池
- 數(shù)據(jù)庫(kù)連接池里,會(huì)有多個(gè)數(shù)據(jù)庫(kù)連接
- 每個(gè)線程使用完連接后,會(huì)放回池子,連接不會(huì)銷毀
- 常用的數(shù)據(jù)庫(kù)連接池有 DBCP、C3P0、Druid
- 數(shù)據(jù)庫(kù)連接池里,會(huì)有多個(gè)數(shù)據(jù)庫(kù)連接
- MySQL 的 連接器
- Java 系統(tǒng)要和MySQL 建立多個(gè)連接,那 MySQL 自然也需要維護(hù)與系統(tǒng)之間的連接
- 所以,MySQL 整體架構(gòu)的第一個(gè)組件就是 連接器
- 所以,MySQL 整體架構(gòu)的第一個(gè)組件就是 連接器
- Java 系統(tǒng)要和MySQL 建立多個(gè)連接,那 MySQL 自然也需要維護(hù)與系統(tǒng)之間的連接
- MySQL 連接器的功能
- 連接器負(fù)責(zé)跟客戶端建立連接、獲取權(quán)限、維持和管理連接
- 連接器內(nèi)部也是一個(gè) 連接池,里面維護(hù)了各個(gè)系統(tǒng)跟這個(gè)數(shù)據(jù)庫(kù)創(chuàng)建的所有連接
- Java 系統(tǒng)連接Mysql 的過程
- 首先完成TCP的三次握手,創(chuàng)建一個(gè)網(wǎng)絡(luò)連接
- 然后開始權(quán)限認(rèn)證,也就是 你的 用戶名 、密碼 是否正確
- 連接成功后,如果沒有后續(xù)動(dòng)作,這個(gè)連接會(huì)處于空閑狀態(tài)
- 空閑一定時(shí)間后,會(huì)自動(dòng)斷開連接,由參數(shù) wait_timeout 控制的,默認(rèn)值是 8 小時(shí)
- 我們現(xiàn)在已經(jīng)知道,我們執(zhí)行SQL,一定要先連接到數(shù)據(jù)庫(kù)。數(shù)據(jù)庫(kù)的 連接器 會(huì)對(duì)系統(tǒng)進(jìn)行權(quán)限認(rèn)證,如果認(rèn)證成功,就創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)連接。
- 那么,連接之后是怎么執(zhí)行SQL語(yǔ)句的呢?
- 一個(gè)基本的知識(shí)點(diǎn),網(wǎng)絡(luò)連接必須要分配給一個(gè)線程去處理
- 由一個(gè) 線程 來監(jiān)聽 和 讀取 Java系統(tǒng)請(qǐng)求的數(shù)據(jù)
- 線程會(huì)從網(wǎng)絡(luò)請(qǐng)求中解析出我們發(fā)送的sql語(yǔ)句
- 線程獲取到了我們寫好的SQL語(yǔ)句,那交給誰(shuí)來執(zhí)行呢
- 其實(shí)在執(zhí)行之前,還有一步,就是查詢緩存
- 之前執(zhí)行過的語(yǔ)句及其結(jié)果可能會(huì)以 key-value 對(duì)的形式,被直接緩存在內(nèi)存中
- key 是查詢的語(yǔ)句
- value 是查詢的結(jié)果
- 如果在緩存中找到 key,那么這個(gè) value 就會(huì)被直接返回給客戶端
- 但是,建議不要使用緩存,往往弊大于利
- 查詢緩存的失效非常頻繁,只要有對(duì)一個(gè)表的更新,這個(gè)表上所有的查詢緩存都會(huì)被清空
- 查詢緩存的命中率會(huì)非常低
- 可以將參數(shù) query_cache_type 設(shè)置成 DEMAND,關(guān)閉緩存
- MySQL 8.0 版本直接將查詢緩存的整塊功能刪掉了
- 所以接下來的圖,我就不畫 查詢緩存 這個(gè)步驟了
- 沒有了查詢緩存這個(gè)功能,我們寫好的SQL,都是交給查詢解析器來分析的
- 解析器
- 我們寫的SQL 語(yǔ)句,人認(rèn)識(shí),但是機(jī)器是不認(rèn)識(shí)的,所以必須要解析我們的語(yǔ)句
- 拿一條SQL舉例
- select id,name from user where id = 10
- 我們的SQL 是由 字符串 和 空格 組成的
- 有些字符串 是 MySQL 的關(guān)鍵字
- MySQL 會(huì)識(shí)別這些關(guān)鍵字
- 語(yǔ)法解析器 會(huì)將 上面的SQL拆解為幾部分
- 要從 user 表里查詢數(shù)據(jù)
- 查詢 id 字段的值,等于 10 的那行數(shù)據(jù)
- 對(duì)查詢的那行數(shù)據(jù),提取出 id 、name 兩個(gè)字段
- 如果語(yǔ)法不對(duì),解析器會(huì)提示我們
- ERROR 1064 (42000): You have an error in your SQL syntax;
- ERROR 1064 (42000): You have an error in your SQL syntax;
- 我們的SQL 是由 字符串 和 空格 組成的
- select id,name from user where id = 10
- 優(yōu)化器
- 經(jīng)過了解析器,MySQL 就知道你要做什么了,在開始執(zhí)行之前,還要先經(jīng)過優(yōu)化器的處理,選擇一個(gè)最優(yōu)路徑
- 我們的表可能創(chuàng)建了多個(gè)索引,或者多表關(guān)聯(lián)(join)的時(shí)候
- 這時(shí),是有多個(gè)路徑可以查詢到結(jié)果的,但是執(zhí)行效率會(huì)不同
- 查詢優(yōu)化器就是干這個(gè)事的,它會(huì)選一個(gè)效率最高的路徑
- 這個(gè)我們后面會(huì)仔細(xì)分析,這里知道它會(huì)選一個(gè)最優(yōu)路徑就好。先了解MySQL的整體架構(gòu),再深究細(xì)節(jié)
- 執(zhí)行器
- MySQL 通過分析器知道了你要做什么
- 通過優(yōu)化器知道了該怎么做,生成執(zhí)行計(jì)劃
- 于是就進(jìn)入了執(zhí)行器階段,負(fù)責(zé)這個(gè)計(jì)劃的執(zhí)行
- 執(zhí)行器 主要是 操作存儲(chǔ)引擎來返回結(jié)果的,我們重點(diǎn)要關(guān)注的是存儲(chǔ)引擎的執(zhí)行原理
- 接下來,我們來研究一下,存儲(chǔ)引擎的架構(gòu)設(shè)計(jì),以及如何基于存儲(chǔ)引擎來完成一條更新語(yǔ)句的執(zhí)行。
- MySQL 有多種存儲(chǔ)引擎,InnoDB、MyISAM等,我們就說最常用的InnoDB。接下來的圖,我就只畫 InnoDB 存儲(chǔ)引擎這部分的了,連接、解釋器、優(yōu)化器會(huì)去掉,不然畫不下了。
- 我們以一條更新操作來看一下 InnoDB 的運(yùn)行流程
- 用這個(gè)SQL舉例:
- update users set name = ‘李四’ where id = 10
- InnoDB 中 重要的內(nèi)存結(jié)構(gòu) Buffer Pool
- Buffer Pool 緩沖池,是 InnoDB 存儲(chǔ)引擎的核心組件。這里會(huì)緩存大量的數(shù)據(jù),查詢時(shí)會(huì)先看 緩沖池 內(nèi)是否有這條數(shù)據(jù),如果有,就可以不用查磁盤了
- 如果 Buffer Pool 中沒有這條數(shù)據(jù),就要先從磁盤中加載進(jìn)來
- Buffer Pool 中的數(shù)據(jù)是緩存頁(yè),磁盤中的表數(shù)據(jù)是數(shù)據(jù)頁(yè),內(nèi)部有其數(shù)據(jù)結(jié)構(gòu)。我們這里忽略,先看一下整體的運(yùn)行流程,之后再分析里面的物理結(jié)構(gòu)。
- undo 日志文件
- 如果我們執(zhí)行一個(gè)更新語(yǔ)句,在沒有提交事務(wù)之前,我們都是可以對(duì)數(shù)據(jù)進(jìn)行回滾的
- undo 日志文件就是保證我們可以回滾數(shù)據(jù)的一個(gè)組件
- 舉例:
- 如果我們要把 id = 10 的數(shù)據(jù)的 name 從 張三 改為 李四
- 第一步是把數(shù)據(jù)加載到 Buffer Pool 里
- 第二步 就要把 id = 10 ,name = 張三 的這條原始數(shù)據(jù),寫到undo日志文件
- 如果數(shù)據(jù)回滾,就會(huì)從 undo 日志文件中讀取原始數(shù)據(jù)恢復(fù)
- 如果我們要把 id = 10 的數(shù)據(jù)的 name 從 張三 改為 李四
- 備注:InnoDB 是個(gè)存儲(chǔ)引擎,步驟2 其實(shí)也是我們上面說到的 執(zhí)行器 來把原始數(shù)據(jù)寫到磁盤上的,后面的步驟,但凡有寫磁盤、讀磁盤的操作,都是執(zhí)行器執(zhí)行的。這里為了畫圖方便,直接連線了
- 然后 執(zhí)行器 會(huì)更新 Buffer Pool 中的緩存數(shù)據(jù)
- 現(xiàn)在,緩存內(nèi)的數(shù)據(jù)已經(jīng)從 張三 更新到 李四 了
- 那么,現(xiàn)在有一個(gè)問題,如果 MySQL 此時(shí)宕機(jī)了會(huì)有問題嗎
- 因?yàn)楝F(xiàn)在還沒有提交事務(wù),代表這條語(yǔ)句還沒執(zhí)行完
- 所以,此時(shí)宕機(jī)沒有關(guān)系,事務(wù)沒提交,重啟后內(nèi)存數(shù)據(jù)就沒了,磁盤數(shù)據(jù)也沒變化
- 磁盤的數(shù)據(jù)也是原始數(shù)據(jù),所以沒關(guān)系
- 我們?cè)趦?nèi)存中修改的數(shù)據(jù),終究要刷到磁盤上的。MySQL 不會(huì)馬上把這條數(shù)據(jù)刷到磁盤上,會(huì)等系統(tǒng)不忙碌時(shí),再刷回去。因?yàn)樗⒋疟P這事,本來實(shí)時(shí)性要就不高,我們查詢的時(shí)候也是基于內(nèi)存的,磁盤是什么數(shù)據(jù)無所謂,只要保證最終一致就好了。
- 我們只有提交事務(wù)后,才能把內(nèi)存修改的數(shù)據(jù)刷到磁盤上
- 提交事務(wù)是一個(gè)過程,這個(gè)過程中我們需要先寫入幾份日志文件,只有這幾個(gè)日志文件都寫成功了,事務(wù)才算提交成功
- 所以,這里開始介紹 InnoDB 存儲(chǔ)引擎中的另一個(gè)組件 Redo log Buffer
- 內(nèi)存中的 Redo log Buffer 配合 磁盤上的 redo log 日志文件,可以在 MySQL 意外宕機(jī)的情況下,恢復(fù)內(nèi)存數(shù)據(jù)的。它會(huì)記錄內(nèi)存中修改的數(shù)據(jù),然后把這些數(shù)據(jù)刷到磁盤上的 redo log 日志中
- 之前,我們已經(jīng)修改了內(nèi)存數(shù)據(jù),在修改完成后,執(zhí)行器就會(huì)向Redo log Buffer 中寫入日志,到這一步為止,我們已經(jīng)執(zhí)行完了這條SQL語(yǔ)句,就差提交事務(wù)
- 如果我們提交事務(wù),第一步就是把 Redo log Buffer 中的日志刷到磁盤上的 redo log 中
- 此時(shí),如果 MySQL 宕機(jī),數(shù)據(jù)是不會(huì)丟失的。重啟后,會(huì)加載磁盤上的 redo log 日志文件,恢復(fù)到內(nèi)存中
- redo log 日志是 偏物理層面的日志,也叫 重做日志。而 binlog 是歸檔日志(這個(gè)后面說)
- 為什么說是偏物理層面的日志,就是說不是給人看的,你看了也不知道修改的啥
- 比如,對(duì)哪些數(shù)據(jù)頁(yè)上的什么數(shù)據(jù)做了什么修改
備注:提交事務(wù),不是一步完成的,是一個(gè)過程。后面的步驟 5、6、7都屬于提交事務(wù)的過程,只要有一步失敗,那提交就是不成功的
- 比如,對(duì)哪些數(shù)據(jù)頁(yè)上的什么數(shù)據(jù)做了什么修改
- 為什么說是偏物理層面的日志,就是說不是給人看的,你看了也不知道修改的啥
- 把 redo log 從內(nèi)存刷到磁盤的策略有三種
- 通過參數(shù) innodb_flush_log_at_trx_commit 來配置 ,默認(rèn)值為 1
- 值為 0 :提交事務(wù)后,不會(huì)把 redo log buffer 里的日志刷到磁盤
- 此時(shí)如果 MySQL 宕機(jī),redo log buffer 內(nèi)數(shù)據(jù)全部丟失
- 值為 1 :提交事務(wù)后立刻把日志刷到磁盤,只要提交事務(wù)成功,那 redo log 一定在磁盤
- 值為 2 :提交事務(wù)后會(huì)把 redo log 先刷到 os cache(操作系統(tǒng)緩存) 里 ,然后 os cache 在適當(dāng)?shù)臅r(shí)機(jī)刷入磁盤
- 在os cache 沒刷磁盤期間,如果 MySQL 宕機(jī),這部分?jǐn)?shù)據(jù)會(huì)丟失
- 值為 0 :提交事務(wù)后,不會(huì)把 redo log buffer 里的日志刷到磁盤
- 我們平時(shí)開發(fā)還是要用 innodb_flush_log_at_trx_commit = 1 ,立刻刷磁盤。保證提交事務(wù)后,數(shù)據(jù)絕對(duì)不會(huì)丟失
- 通過參數(shù) innodb_flush_log_at_trx_commit 來配置 ,默認(rèn)值為 1
- redo log 是偏物理層面的日志。如果發(fā)生數(shù)據(jù)庫(kù)操作失誤,我們不能根據(jù)這個(gè)來恢復(fù)數(shù)據(jù)。我們需要用 binlog 來恢復(fù),binlog 是偏邏輯性的日志
- binlog
- binlog 也叫 歸檔日志,是邏輯性的日志
- 如:對(duì) users 表的 id = 10 的一行數(shù)據(jù)做了更新操作
- binlog 不是 InnoDB 存儲(chǔ)引擎特有的日志文件,是屬于 MySQL Server 自己的日志文件
- 我們開始提交事務(wù),第一步是把 redo log 日志刷到磁盤, 接下來執(zhí)行器還要繼續(xù)寫 binlog 日志到磁盤
- binlog 刷磁盤有兩種策略,通過 sync_binlog 參數(shù)來配置,默認(rèn)值 0
- 值為 0 :先刷到 os cache 緩存,然后不定時(shí)刷入磁盤
- 如果宕機(jī),可能會(huì)丟失數(shù)據(jù)
- 值為 1 :直接刷到 磁盤文件中 ,是要提交事務(wù)成功,數(shù)據(jù)一定不會(huì)丟失
- 值為 0 :先刷到 os cache 緩存,然后不定時(shí)刷入磁盤
- binlog 也叫 歸檔日志,是邏輯性的日志
- 最后,是 事務(wù)提交的最后一步
- 執(zhí)行器 會(huì)把本次更新對(duì)應(yīng)的 binlog 日志的文件名 和 本次更新的 binlog 日志在文件中的位置,都寫入 redo log 日志文件中
- 同時(shí),還會(huì)寫入一個(gè) commit 標(biāo)記
- 只有完成了這一步,才算是 事務(wù)提交成功
- 為什么要在 redo log 中寫入 commit 標(biāo)記呢?
- 用來保證 redo log 和 bin log 的數(shù)據(jù)一致性
- 舉例:
- 如果完成了第5步,刷入了 redo log 后,MySQL 宕機(jī)了,那 bin log 就沒法寫入 commit 標(biāo)記,那這條數(shù)據(jù)沒有 commit 標(biāo)記,就是無效的,提交事務(wù)失敗
- 如果第6步,刷入了 binlog 后,MySQL 宕機(jī)了,一樣沒有 commit 標(biāo)記,也是無效的
- 現(xiàn)在,本條更新語(yǔ)句已經(jīng)提交了事務(wù),更新完畢了
- 此時(shí),內(nèi)存上的數(shù)據(jù) 已經(jīng)是 更新過的 name = 李四 ,磁盤上是 name = 張三
- 此時(shí),MySQL 宕機(jī)是無所謂的,數(shù)據(jù)不會(huì)丟失,重啟后會(huì)從redo log 加載到緩沖池
- 然后,是最后一個(gè)步驟
- MySQL 有一個(gè)后臺(tái)的 IO 線程,在之后的某個(gè)時(shí)間,會(huì)隨機(jī)的把內(nèi)存 Buffer pool 中修改的臟數(shù)據(jù)刷回磁盤的數(shù)據(jù)文件中
- 臟數(shù)據(jù):就是內(nèi)存和磁盤不一致,但是沒有什么影響
- 臟數(shù)據(jù):就是內(nèi)存和磁盤不一致,但是沒有什么影響
- MySQL 有一個(gè)后臺(tái)的 IO 線程,在之后的某個(gè)時(shí)間,會(huì)隨機(jī)的把內(nèi)存 Buffer pool 中修改的臟數(shù)據(jù)刷回磁盤的數(shù)據(jù)文件中
- 到現(xiàn)在,我們已經(jīng)知道了 MySQL 的整體運(yùn)行流程,和內(nèi)部的運(yùn)行原理,MySQL 的全貌我們已經(jīng)看見了。我們?cè)谀X海中要有下面這張圖
接下來,我們重點(diǎn)研究每個(gè)組件的底層原理,深入的分析里面的細(xì)節(jié)
總結(jié)
以上是生活随笔為你收集整理的MySQL 内核原理分析(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 认清GPU的流处理器作用
- 下一篇: 数据库学习笔记2