MySQL----數據庫底層實現原理


1. MySQL構成: 上層的MySQL Server和下層的存儲引擎構成。當一條SQL語句過來的時候,是首先由MySQL Server的連接器負責建立客戶端和服務器的連接,然后經過權限判斷此查詢是否有權限;然后經過詞法分析語法分析判斷語句是否有語法錯誤;然后經過優化器來優化查詢語句使用適當的索引;最后調用存儲引擎的接口執行相應的操作。

2. 存儲引擎:MyISAM和InnoDB,InnoDB主要處理事務比較多的數據存儲。

3. InnoDB存儲引擎的實現:

(1)底層數據存儲方式:采用頁的方式存儲數據記錄,索引和數據分開存儲。

(2)為了使查詢速度快,需要建立索引去查詢,采用何種方式建索引?采用B+樹的方式存儲,可以減少樹的高度,減少磁盤IO次數。

(3)聚簇索引和輔助索引?聚簇索引就是每個表通過主鍵來建立的,非葉節點上都是主鍵索引,葉節點上是記錄。每個表只有一個聚簇索引。輔助索引是在其他字段上建立的索引,可以有多個,非葉節點上存儲索引,而葉節點存儲主鍵值。輔助索引可以通過主鍵值來找到記錄。

(4)事務特性?ACID,原子性、一致性、隔離性、持久性。

(5)原子性如何保證?通過undo log,mvcc

數據在處理之前,如果數據在處理中變化了,出現了異常,需要我們能把數據恢復到處理之前的樣子,所以在事務開始前,記錄下數據記錄的原始值。然后開始事務,代表事務開始處理了,如果是查詢的操作,按理來說如果查詢的那條記錄正在被更新,那這條記錄是被加寫鎖的,無論再讀還是寫都會阻塞,這顯然會降低效率,所以InnoDB對記錄增加了版本的概念,當記錄在更新的時候,我們可以讀這條記錄之前的版本。這就是MVCC引入的原因。數據記錄在不斷被更新的過程中,都會將改動之前的記錄添加到undo log。就是這個記錄的之前的版本,這個版本有倆作用,一個上我門上面提到的可以提高並發的讀寫的效率;另一個則是可以用來回滾數據。

(6)隔離性如何保證?通過加鎖,innodb的鎖有表鎖、行鎖、間隙鎖。

表鎖是對表中每個記錄的主鍵索引加鎖;如果是主鍵索引或是唯一索引,在主鍵索引上加行鎖。如果是非唯一索引,需要在主鍵上加索引,以及查詢的范圍上加間隙鎖。

(7)事務的隔離級別?RU, RC,RR,Serializable

RU是寫的時候加寫鎖,如果有兩個事務,一個事務修改未提交,另一個事務則會被阻塞。

RC和RR有了MVCC,可以支持在一個事務寫的時候,另一個事務的讀不受影響,當時這個讀是指快照讀(select...),而當前讀(select for update)還是會被阻塞。其他的更新操作也是當前讀,也會被阻塞。

Serializable是串行化的方式,讀寫都會阻塞。

RR通過加間隙鎖解決了幻讀的問題。

(8)持久性如何保證?通過redo log,WAL。保證在寫入數據前先把寫入數據的日志持久化。日志先行的工作方式,減少磁盤IO,文件是順序IO,而數據是隨機IO。

(9)binlog和redo log的區別?binlog是在上層事務提交以后產生的,邏輯上的日志,每種存儲引擎都有binlog。redo log是記錄數據頁的變更,物理上的日志。

 

一個SQL更新操作的完整過程:

1. 事務開始后,數據進行更新操作,加載緩存數據,如果有的話就從緩存中獲取,沒有的話需要從磁盤上拉取,放到buffer pool中;

2. 數據的舊值寫入到undo log,方便回滾。

3. 更新數據頁里的數據記錄,此時數據頁面變為臟頁,數據頁在buffer pool里,buffer pool 中的頁可以區分為freelist,lrulist, flushlist,當從磁盤中加載一個數據的時候,需要從freelist中申請一個頁放在lrulist中,當頁被更新后,也會被加到flushlist。lrulist的長度有限,新的訪問的頁會放在前面,后面的頁會慢慢被淘汰,如果淘汰的頁面正好是臟頁的話需要把臟頁的內容同步到磁盤上。

4. 然后把數據的更新記錄在redo log buffer上,redo log bufer也不能每次都直接flush到磁盤,那樣效率太低,在內存中有個redo log buffer,會存儲redo log。可通過三種方式設置redo log buffer的內容同步到磁盤的redo log的方式,一種是每隔一秒一次;第二種是每次事務在commit時候,會把redo log buffer的內容更新到系統的緩存,系統緩存會定時把內容sync到磁盤;第三種是每次提交時候都把他同步到磁盤。默認是第二種。buffer pool中的臟頁會在一定是時機同步到磁盤:

5. 內存數據頁的更新內容記錄在 redolog buffer中,此時,buffer中的這條語句狀態為prepare。然后告知執行器執行完成了,隨時可以提交事務。

6. server層提交事務時,會先將這個操作的日志寫入binlog buffer中,

7. 再調用引擎的事務提交接口,引擎會將剛寫入的redolog記錄狀態修改為commit。更新完成。

8. 數據刷新到磁盤。定期或是bufferpool滿了。或是服務down機。

 

 

 

 總結:更新數據的基本過程是先加載要更新的數據到磁盤中,然后寫undo log,更新內存數據,寫redo log,redo log落盤。數據會定期被更新到磁盤。

那未提交的數據也可能被同步到磁盤?參考https://blog.csdn.net/Singularinty/article/details/80747290,看不同的策略,steal + no force組合允許,所以需要redo log和undolog來恢復數據。如果是nosteal + force策略則不會出現未commit的數據出現在磁盤上,就不需要redo log和undo log 來做數據恢復。

個人理解:用redo log把需要更新的數據以記事本的方式記錄,這個記錄是順序的,我們可以很快記錄我要更新第N頁第N個記錄為XXX。而如果更新數據的話我們需要先找到這個第N頁的第N個記錄的地址,會比較慢,所以InnoDB采用這種方式可以把需要更新的數據先記錄下來。

redo log開始隨着事務中的數據的操作,寫在redo log buff中,每次操作都會記錄一個,然后在事務提交時候,先把他設置為prepare返回給執行器,執行器記錄完binlog以后,執行最終的commit,redolog把 buffer中的內容(也是做后的結果)更新到os cache。然后os cache以他的頻率通常是1s,fsync到磁盤。那么可以說,只要事務提交的話,就一定會進入binlog,redo log。而數據的更新則是一方面定時更新到磁盤,另一方面在不得已需要處理臟頁的時候更新到磁盤。

force會在刷新完redo log以后也把數據的更新刷新到磁盤,就不有事務提交了但是數據還沒有持久化的問題。

no-steal會保證沒有commit的數據不會刷新到磁盤,就不會有數據已經在磁盤上了,但是還沒有提交事務的情況。

就是數據更新到磁盤的線程和redo log刷新到磁盤的線程是兩個獨立的線程,兩個線程執行先后不一樣會造成數據和redo log的不一致。數據到了但是redo log沒到或者redo log到了但是數據沒到。這兩種問題,如果是數據到了redo log沒到,就需要通過undo log回滾。而redo log到了,數據沒到,就需要用redo log重做。

 redo log不能一直追加,要不然恢復的時候數據量太大效率很低,redolog有大小限制,可以重復利用。循環寫入,假設編號依此是,1、2、3、4,從1到4順序寫入,在4寫入以后在從1開始寫,但是1的內容需要是里面數據的修改已經同步到磁盤的了。所以redo log file上有兩個標志位,一個是checkpoint,一個是write_pos,checkpoint以前的代表數據已經同步到磁盤,checkpoint后面是還沒有同步到磁盤。

 

 參考文檔:

https://www.jianshu.com/p/dbbd8d601f8c

https://blog.51cto.com/u_15127629/2736150

https://blog.csdn.net/Singularinty/article/details/80747290

https://www.zhihu.com/question/267595935

https://xie.infoq.cn/article/3cae3ed498b41d3da8a080c23


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM