相關(guān)基本概念及DDL語句在這里就不贅述了,本篇文章主要聊一聊mysql底層的東西。
一、架構(gòu)
mysql的架構(gòu),主要分為server層和引擎層,大體分布如下:

MySQL基架大致包括如下幾大模塊組件:
(1)MySQL向外提供的交互接口(Connectors)
(2)管理服務(wù)組件和工具組件(Management Service & Utilities)
(3)連接池組件(Connection Pool)
(4)SQL接口組件(SQL Interface)
(5)查詢分析器組件(Parser)
(6)優(yōu)化器組件(Optimizer)
(7)緩存主件(Caches & Buffers)
(8)插件式存儲引擎(Pluggable Storage Engines)
(9)物理文件(File System)

(一)MySQL向外提供的交互接口(Connectors)
Connectors組件,是MySQL向外提供的交互組件,如java,.net,php等語言可以通過該組件來操作SQL語句,實現(xiàn)與SQL的交互。
(二)管理服務(wù)組件和工具組件(Management Service & Utilities)
提供對MySQL的集成管理,如備份(Backup),恢復(fù)(Recovery),安全管理(Security)等
(三)連接池組件(Connection Pool)
負(fù)責(zé)監(jiān)聽對客戶端向MySQL Server端的各種請求,接收請求,轉(zhuǎn)發(fā)請求到目標(biāo)模塊。每個成功連接MySQL Server的客戶請求都會被創(chuàng)建或分配一個線程,該線程負(fù)責(zé)客戶端與MySQL Server端的通信,接收客戶端發(fā)送的命令,傳遞服務(wù)端的結(jié)果信息等。
(四)SQL接口組件(SQL Interface)
接收用戶SQL命令,如DML,DDL和存儲過程等,并將最終結(jié)果返回給用戶。
(五)查詢分析器組件(Parser)
首先分析SQL命令語法的合法性,并嘗試將SQL命令分解成數(shù)據(jù)結(jié)構(gòu),若分解失敗,則提示SQL語句不合理。
(六)優(yōu)化器組件(Optimizer)
對SQL命令按照標(biāo)準(zhǔn)流程進行優(yōu)化分析。
(七)緩存主件(Caches & Buffers)
緩存和緩沖組件
(八)MySQL存儲引擎
(九)物理文件(File System)
實際存儲MySQL 數(shù)據(jù)庫文件和一些日志文件等的系統(tǒng),如Linux,Unix,Windows等。
二、一個查詢的主要流程

1、連接器
- 連接器負(fù)責(zé)跟客戶端建立連接、獲取權(quán)限、維持和管理連接
- 在完成經(jīng)典的TCP握手后,連接器就要開始認(rèn)證你的身份,這個時候用的就是你輸入的用戶名和密碼
- 一個用戶成功建立連接之后,即使你用管理員賬號對這個用戶的權(quán)限做了修改,也不會影響已經(jīng)存在的連接的權(quán)限。修改完成之后,只有再新建的連接才會使用新的權(quán)限配置
2、查詢緩存
- 拿到一個查詢請求后,會先到查詢緩存中查看
- 之前執(zhí)行過的語句及其結(jié)果可能會以key-value的形式被直接緩存在內(nèi)存中。key是查詢的語句,value是結(jié)果。如果你的查詢能夠直接在這個緩存中找到key,那么這個value就會直接返回給客戶端
3、分析器
- 如果沒有命中查詢緩存,就要開始真正執(zhí)行語句
- 分析器會先做“詞法分析”。你輸入的是由多個字符串和空格組成的一條sql語句,mysql需要識別出里面的字符串分別是什么,代表什么,將整個sql語句打碎成一個個單詞(token)
- 做完了這些識別之后,就要做“語法分析”。根據(jù)詞法分析的結(jié)果,語法分析器會根據(jù)語法規(guī)則,判斷你輸入的這個sql語句是否滿足sql語法,生成對應(yīng)的抽象語法樹AST,提供給優(yōu)化器
4、優(yōu)化器
- 優(yōu)化器是在表里面有多個索引的時候,決定使用哪個索引
-
或者在一個語句中有多表關(guān)聯(lián)的時候,決定各個表的連接順序
舉個例子 - 這兩種執(zhí)行方法的邏輯結(jié)果是一樣的,但是執(zhí)行的效率會有所不同,而優(yōu)化器的作用就是決定選擇使用哪一個方案。
5、執(zhí)行器
- 先判斷一下你對這個表有沒有執(zhí)行查詢的權(quán)限
- 有權(quán)限,就在打開表的時候,執(zhí)行器會根據(jù)表的引擎定義,去使用這個引擎提供的接口
- 在這個例子中,sex字段沒有索引,那么執(zhí)行器的執(zhí)行流程是這樣的:
調(diào)用InnoDB引擎接口取這個表的第一行,判斷ID值是不是10,如果不是則跳過,如果是則將這行存在結(jié)果集中;調(diào)用引擎接口取“下一行”,重復(fù)相同的判斷邏輯,直到取到這個表的最后一行;執(zhí)行器將上述遍歷過程所有滿足條件的行組成的記錄集作為結(jié)果集返回給客戶端
三、行鎖
- InnoDB是支持行鎖的,默認(rèn)情況下是采用行級鎖
- MylSAM引擎不支持行鎖
InnoDB實現(xiàn)了以下兩種類型的行鎖: -
共享讀鎖(s):允許一個事務(wù)去讀一行,阻止其他事務(wù)獲得相同數(shù)據(jù)集的排它鎖 -
排他寫鎖(x):允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其他事務(wù)取得相同數(shù)據(jù)集的共享讀鎖和排他寫鎖。對于update、insert、delete語句,InnoDB會自動給涉及數(shù)據(jù)集加排他鎖(x)
行鎖的兩階段鎖協(xié)議
- 在InnoDB事務(wù)中,行鎖是需要的時候才加的,但并不是不需要了就立即釋放,而是需要等到事務(wù)結(jié)束的時候才釋放。這個就是兩階段鎖協(xié)議
- 如果你的事務(wù)中需要鎖多個行,要把最可能造成鎖沖突、最可能影響并發(fā)度的鎖盡量往后放
假如有一個類似信用卡取現(xiàn)的業(yè)務(wù),需要用戶a向銀行b取現(xiàn)100元,這個業(yè)務(wù)涉及以下操作:
1.從用戶a賬戶增加100元
2.從銀行b賬戶減少100元
3.記錄一條交易日志
個人賬戶一般情況下,只有一個人在使用,但是銀行賬戶在a用戶使用的同時有可能c用戶、d用戶同時向它取現(xiàn),這些事務(wù)沖突的就是操作2,那么根據(jù)兩階段鎖協(xié)議,就需要把操作2放在事務(wù)的最后執(zhí)行,這就最大程度地減少了事務(wù)之間的鎖等待,提升了并發(fā)度
- InnoDB行鎖是通過索引上的索引項加鎖來實現(xiàn)的,InnoDB這種行鎖實現(xiàn)特點意味著:只有通過索引條件檢索數(shù)據(jù),InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!
- 這里的行鎖退化表鎖是會鎖上聚簇索引中的所有記錄,并且會鎖上聚簇索引中的所有間隙鎖
- 只有執(zhí)行計劃真正使用了索引,才能使用行鎖:即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由mysql通過判斷不同執(zhí)行計劃的代價來決定的。如果mysql認(rèn)為全局掃描效率更高,比如一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖沖突時,別忘了檢查slq的執(zhí)行計劃(可以通過explain檢查sql的執(zhí)行計劃),以確認(rèn)是否真正使用了索引
- mysql的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然多個session是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現(xiàn)沖突的
四、表鎖
先來看一段代碼:
SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE,t2 READ......;
[do something with tables t1 and t2 here]
COMMIT;
UNLOCK TABLES;
- 在用LOCK TABLES對InnoDB表加鎖時要注意,要將AUTOCOMMIT設(shè)為0,否則mysql不會給表枷鎖;
- 事務(wù)結(jié)束前,不要用UNLOCK TABLES釋放表鎖,因為UNLOCK TABLES會隱含地提交事務(wù);
- COMMIT或ROLLBACK并不能釋放用LOCK TABLES加的表級鎖,必須用UNLOCK TABLES釋放表鎖。
五、元數(shù)據(jù)鎖MDL
在Mysql5.5版本中引入了MDL,當(dāng)對一個表做增刪改查操作的時候,加MDL讀鎖;當(dāng)要對表做結(jié)構(gòu)變更操作的時候,加MDL寫鎖。不用顯式加鎖
六、InnoDB索引模型
- mysql中索引分為主鍵索引和非主鍵索引
- 主鍵索引的葉子節(jié)點存的是整行數(shù)據(jù)。在InnoDB中,主鍵索引也被稱為
聚簇索引 - 非主鍵索引的葉子節(jié)點存放的內(nèi)容是主鍵的值。在InnoDB中,非主鍵索引也被稱為
二級索引
兩者查詢的區(qū)別 - 如果語句是
select * from T where ID = 500,即主鍵查詢方式,則只需要搜索ID這棵B+樹 - 如果語句是
select * from T where k = 5,即普通索引查詢方式,則需要先搜索k索引樹,得到ID的值為500,再到ID索引樹搜索一次。這個過程稱為回表 - 基于非主鍵索引的查詢需要多掃描一棵索引樹。
七、隔離級別

八、bin log
- binlog是mysql的server層實現(xiàn)的,記錄的是這個語句的原始邏輯
- binlog是可以追加寫入的?!白芳訉憽笔侵竍inlog文件寫到一定大小會切換到下一個,并不會覆蓋以前的日志
日志恢復(fù)
- 前提是數(shù)據(jù)定期做備份,保證musql中一段時間的binlog
- 當(dāng)發(fā)生誤操作進行數(shù)據(jù)恢復(fù)時,先找到最近一次全量備份,恢復(fù)到數(shù)據(jù)庫
- 從備份的時間點開始,將備份的binlog依次取出來,重放到誤刪除表之前的那個時刻。
九、redo-log
先看一段sql語句:
UPDATE person SET person_name = 'heiwu' WHERE id = 5;
- redo log是保存在引擎層的
- 如果每一次的更新操作都要寫進磁盤,然后磁盤也要找到對應(yīng)的那條記錄,然后再更新,整個IO成本、查找成本都很高
- mysql使用wal技術(shù)來優(yōu)化更新操作,wal的全程是Write-Ahead logging,它的關(guān)鍵點就是先寫日志,再寫磁盤。
- 當(dāng)有一條記錄需要更新的時候,InnoDB引擎就會先把記錄寫到
redo log中去,并更新內(nèi)存,這個時候更新就算完成了。同時,InnoDB引擎會在適當(dāng)?shù)臅r候,將這個操作記錄更新到磁盤里面,而這個更新往往是系統(tǒng)比較空閑的時候做 - InnoDB的redo log是固定大小的,比如可以配置為一組4個文件,每個文件的大小是1GB,那么這塊文件總共就可以記錄4GB的操作
- 有了redo log,InnoDB就可以保證即使數(shù)據(jù)庫發(fā)生異常重啟,之前提交的記錄都不會丟失,這個能力稱之為crash-safe。
十、redo-log vs binlog
還是上面的那個更新操作語句:
UPDATE person SET person_name = 'heiwu' WHERE id = 5;
- 執(zhí)行器先找引擎(InnoDB)取ID=5這一行,ID是主鍵,引擎直接用樹搜索找到這一行。如果ID=5這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器;否則,就需要先從磁盤讀入緩存,然后再返回;
- 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個值改為“heiwu”,比如原得到一行新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù)
- 引擎將這行新數(shù)據(jù)更新到內(nèi)存中,同時將這個更新操作記錄到
redo log中,此時redo log處于prepare狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時可以提交事務(wù) - 執(zhí)行器生成這個操作的
binlog,并把binlog寫入到磁盤中去 - 執(zhí)行器調(diào)用引擎的事務(wù)提交接口,引擎把剛剛寫入的
redo log改成提交(commit)狀態(tài),更新完成
