日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

這篇文章會通過一條SQL更新語句的執行流程讓大家清楚地明白:

  • 什么是InnoDB頁?緩存頁又是什么?為什么這么設計?
  • 什么是表空間?不同存儲引擎的表在文件系統的底層表示上有什么區別?
  • Buffer Pool是什么?為什么需要?有哪些我們需要掌握的細節?
  • MySQL的三種日志文件redo日志、undo日志、binlog分別是什么?為什么需要這么多種類型的日志?

正文開始!

之前我們講過了一條SQL查詢語句是如何執行的,那么插入(INSERT)、更新(UPDATE)和刪除(DELETE)操作的流程又是什么樣子呢?

其實對于MySQL而言,只有兩種通常意義的操作,一種是Query(查詢),另一種是Update(更新),后者包含了我們平常使用的INSERT、UPDATE和DELETE操作。

那么MySQL的更新流程和查詢流程有什么區別呢?

其實基本的流程是一致的,也要經過處理連接、解析優化、存儲引擎幾個步驟。主要區別在更新操作涉及到了MySQL更多的細節。

注:我們接下來的所有描述,針對的都是InnoDB存儲引擎,如果涉及到其他存儲引擎,將會特殊說明

1. 一些需要知道的概念

對于MySQL任何存儲引擎來說,數據都是存儲在磁盤中的,存儲引擎要操作數據,必須先把磁盤中的數據加載到內存中才可以。

那么問題來了,一次性從磁盤中加載多少數據到內存中合適呢?當獲取記錄時,InnoDB存儲引擎需要一條條地把記錄從磁盤中讀取出來嗎?

當然不行!我們知道磁盤的讀寫速度和內存讀寫速度差了幾個數量級,如果我們需要讀取的數據恰好運行在磁盤的不同位置,那就意味著會產生多次I/O操作。

因此,無論是操作系統也好,MySQL存儲引擎也罷,都有一個預讀取的概念。概念的依據便是統治計算機界的局部性原理。

空間局部性:如果當前數據是正在被使用的,那么與該數據空間地址臨近的其他數據在未來有更大的可能性被使用到,因此可以優先加載到寄存器或主存中提高效率

就是當磁盤上的一塊數據被讀取的時候,我們干脆多讀一點,而不是用多少讀多少。

1.1 InnoDB頁

InnoDB存儲引擎將數據劃分為若干個頁,以頁作為磁盤和內存之間交互的最小單位。InnoDB中頁的大小默認為16KB。也就是默認情況下,一次最少從磁盤中讀取16KB的數據到內存中,一次最少把內存中16KB的內容刷新到磁盤上。

對于InnoDB存儲引擎而言,所有的數據(存儲用戶數據的索引、各種元數據、系統數據)都是以頁的形式進行存儲的。

1.2 表空間

為了更好地管理頁,MySQL又設計了「表空間」的概念。表空間又有很多類型,具體類型我們不需要知道,我們只需要知道,一個表空間可以劃分成很多個InnoDB頁,InnoDB表數據都存儲在某個表空間的頁中。

為了方便我們定位,MySQL貼心地為表空間設計了一個唯一標識——表空間ID(space ID)。同理,InnoDB頁也有自己的唯一編號——頁號(page number)。

因此,我們可以這么認為。給定表空間ID和頁號以及頁的偏移量,我們就可以定位到InnoDB頁的某條記錄,也就是數據庫表的某條記錄。

1.2.1 數據表在文件系統中的表示

為了更好地讓大家理解這個抽象的概念,我創建了名為test的數據庫,在其下分別創建了3張表t_user_innodb,t_user_myisam,t_user_memory,對應的存儲引擎分別為InnoDB、MyISAM、MEMORY。

進入MySQL的數據目錄,找到test目錄,看一下test數據庫下所有表對應的本地文件目錄

drwxr-x--- 2 mysql mysql 4096 Jan 26 09:28 .

drwxrwxrwt 6 mysql mysql 4096 Jan 26 09:24 ..

-rw-r----- 1 mysql mysql 67 Jan 26 09:24 db.opt

-rw-r----- 1 mysql mysql 8556 Jan 26 09:28 t_user_innodb.frm

-rw-r----- 1 mysql mysql 98304 Jan 26 09:28 t_user_innodb.ibd

-rw-r----- 1 mysql mysql 8556 Jan 26 09:27 t_user_memory.frm

-rw-r----- 1 mysql mysql 0 Jan 26 09:28 t_user_myisam.MYD

-rw-r----- 1 mysql mysql 1024 Jan 26 09:28 t_user_myisam.MYI

-rw-r----- 1 mysql mysql 8556 Jan 26 09:28 t_user_myisam.frm

1.2.2 InnoDB是如何存儲表數據的

「表空間」是InnoDB存儲引擎獨有的概念。

我們看到t_user_innodb表在數據庫對應的test目錄下會生成以下兩個文件

  • t_user_innodb.frm
  • t_user_innodb.ibd

其中,t_user_innodb.ibd就是t_user_innodb表對應的表空間在文件系統上的表示;t_user_innodb.frm用來描述表的結構,如表有哪些列,列的類型是什么等。

1.2.3 MyISAM是如何存儲表數據的

和InnoDB不同,MyISAM沒有表空間的概念,表的數據和索引全都直接存放在對應的數據庫子目錄下,可以看到t_user_myisam對應了三個文件

  • t_user_myisam.MYD
  • t_user_myisam.MYI
  • t_user_myisam.frm

其中,t_user_myisam.MYD表示表的數據文件,也就是我們實際看到的數據表的內容;t_user_myisam.MYI表示表的索引文件,為該表創建的索引都會存放在這個文件中;t_user_myisam.frm用來描述表的結構。

1.2.4 MEMORY是如何存儲表數據的

MEMORY存儲引擎對應的數據表只有一個描述表結構的文件t_user_memory.frm。

2. 緩沖池Buffer Pool

為了更好的利用局部性原理帶給我們的優勢,InnoDB在處理客戶端請求時,如果需要訪問某個頁的數據,會把該數據所在的頁的全部數據加載到內存中。哪怕是只需要訪問一個頁中的一條數據,也需要加載整個頁。

從磁盤中加載數據到內存中的操作太昂貴了!有什么辦法可以提高數據操作的效率呢?緩存!

為了緩存磁盤的頁,InnoDB在MySQL服務器啟動時會向操作系統申請一片連續的內存區域,這片內存區域就是Buffer Pool

很容易理解,為了更好地緩存頁數據,Buffer Pool對應的一片連續內存空間也被劃分為若干個頁,而且默認情況下,Buffer Pool頁的大小和InnoDB頁大小一樣,都是16KB。為了區分兩種不同的頁,我們將Buffer Pool中的頁面稱為緩沖頁。

讀取數據的時候,InnoDB先判斷數據是否在Buffer Pool中,如果是,則直接讀取數據進行操作,不用再次從磁盤加載;如果不是,則從磁盤加載到Buffer Pool中,然后讀取數據進行操作。

修改數據的時候,也是將數據先寫到Buffer Pool緩沖頁中,而不是每次更新操作都直接寫入磁盤。當緩沖頁中的數據和磁盤文件不一致的時候,緩沖頁被稱為臟頁。

那么臟頁是什么時候被同步到磁盤呢?

InnoDB中有專門的后臺線程每隔一段時間會把臟頁的多個修改刷新到磁盤上,這個動作叫做「刷臟」。

3. redo日志

3.1 為什么需要redo日志

不定時刷臟又帶來一個問題。如果臟頁的數據還沒有刷新到磁盤上,此時數據庫突然宕機或重啟,這些數據就會丟失。

首先想到的最簡單粗暴的解決方案就是在事務提交之前,把該事務修改的所有頁面都刷新到磁盤。但是上文說過,頁是內存和磁盤交互的最小單位,如果只修改了1個字節,卻要刷新16KB的數據到磁盤上,不得不說太浪費了,此路不通!

所以,必須要有一個持久化的措施。

為了解決這個問題,InnoDB把對所有頁的更新操作(再強調一遍,包含INSERT、UPDATE、DELETE)專門寫入一個日志文件。

當有未同步到磁盤中的數據時,數據庫在啟動的時候,會根據這個日志文件進行數據恢復。我們常說的關系型數據庫的ACID特性中的D(持久性),就是通過這個日志來實現的。

這個日志文件就是大名鼎鼎的redo日志。

「re」在英文中的詞根含義是“重新”,redo就是「重新做」的意思,顧名思義就是MySQL根據這個日志文件重新進行操作

這就出現了一個有意思的問題,刷新磁盤和寫redo日志都是進行磁盤操作,為什么不直接把數據刷新到磁盤中呢?

3.2 磁道尋址

我們需要稍微了解一下磁道尋址的過程。磁盤的構造如下圖所示。

每個硬盤都有若干個盤片,上圖的硬盤有4個盤片。

每個盤片的盤面上有一圈圈的同心圓,叫做「磁道」。

從圓心向外畫直線,可以將磁道劃分為若干個弧段,每個磁道上一個弧段被稱之為一個「扇區」(右上圖白色部分)。數據是保存在扇區當中的,扇區是硬盤讀寫的最小單元,如果要讀寫數據,必須找到對應的扇區,這個過程叫做「尋址」。

3.2.1 隨機I/O

如果我們需要的數據是隨機分散在磁盤上不同盤片的不同扇區中,那么找到相應的數據需要等到磁臂旋轉到指定的盤片然后繼續尋找對應的扇區,才能找到我們所需要的一塊數據,持續進行此過程直到找完所有數據,這個就是隨機I/O,讀取數據速度非常慢。

3.2.2 順序I/O

假設我們已經找到了第一塊數據,并且其他所需的數據就在這一塊數據之后,那么就不需要重新尋址,可以依次拿到我們所需的數據,這個就叫順序 I/O。

現在回答之前的問題。因為刷臟是隨機I/O,而記錄日志是順序I/O(連續寫的),順序I/O效率更高,本質上是數據集中存儲和分散存儲的區別。因此先把修改寫入日志文件,在保證了內存數據的安全性的情況下,可以延遲刷盤時機,進而提升系統吞吐。

3.3 redo日志的系統變量

redo日志位于MySQL數據目錄下,默認有ib_logfile0和ib_logfile1兩個文件,如下圖所示。

可以發現,兩個redo日志文件的大小都是50331648,默認48MB。為什么這個大小是固定的呢?因為如果我們要使用順序I/O,就必須在申請磁盤空間的時候一次性決定申請的空間大小,這樣才能保證申請的磁盤空間在地址上的連續性。

這也就決定了redo日志的舊數據會被覆蓋,一旦文件被寫滿,就會觸發Buffer Pool臟頁到磁盤的同步,以騰出額外空間記錄后面的修改。

可以通過以下指令查看redo日志的系統屬性。

mysql> show variables like 'innodb_log%';

+-----------------------------+----------+

Variable_name | Value |

+-----------------------------+----------+

innodb_log_buffer_size | 16777216 |

innodb_log_checksums | ON |

innodb_log_compressed_pages | ON |

innodb_log_file_size | 50331648 |

innodb_log_files_in_group | 2 |

innodb_log_group_home_dir | ./ |

innodb_log_write_ahead_size | 8192 |

+-----------------------------+----------+

參數名稱含義innodb_log_file_size指定每個redo日志文件的大小,默認48MBinnodb_log_files_in_group指定redo日志文件的數量,默認2innodb_log_group_home_dir指定redo文件的路徑,如果不指定,則默認為datadir目錄

介紹到這里,讀者朋友可以發現,我們剛才探索的是如何讓已經提交的事務保持持久化,但是如果某些事務偏偏在執行到一半的時候出現問題怎么辦?

事務的原子性要求事務中的所有操作要么都成功,要么都失敗,不允許存在中間狀態。就好比我在寫這篇文章的時候,會時不時地敲一下ctrl+Z返回到上一步或者過去好幾步之前的狀態,MySQL也需要“留一手”,把事務回滾時需要的東西都記錄下來。

比如,插入數據的時候,至少應該把新增的這條記錄的主鍵的值記錄下來,這樣回滾的時候只要把這個主鍵值對應的記錄刪除就可以了。

MySQL又一個鼎鼎大名的日志——undo日志,正式登場!

4. undo日志

undo log(撤銷日志或回滾日志)記錄了事務發生之前的數據狀態,分為insert undo log和update undo log。

如果修改數據時出現異常,可以用 undo log來實現回滾操作(保持原子性)。可以理解為undo日志記錄的是反向的操作,比如INSERT操作會記錄DELETE,UPDATE會記錄UPDATE之前的值,和redo日志記錄在哪個物理頁面做了什么操作不同,所以這是一種邏輯格式的日志。

undo日志和redo日志與事務密切相關,被統稱為「事務日志」。

關于undo日志,我們目前只需要了解這么多即可

5. SQL更新語句的執行總結——初版

有了事務日志之后,我們來簡單總結一下更新操作的流程,這是一個簡化的過程。

name 原值是chanmufeng。

update t_user_innodb set name ='chanmufeng1994' where id = 1;

  1. 事務開始,從內存(Buffer Pool)或磁盤取到包含這條數據的數據頁,返回給 Server 的執行器;
  2. Server 的執行器修改數據頁的這一行數據的值為 chanmufeng1994;
  3. 記錄 name=chanmufeng 到undo log;
  4. 記錄 name=chanmufeng1994到redo log;
  5. 調用存儲引擎接口,記錄數據頁到Buffer Pool(修改 name=penyuyan);
  6. 事務提交。

6. binlog日志

之前我們講過,從MySQL整體架構來看,其實可以分成兩部分

  • Server 層,它主要做的是 MySQL功能層面的事情,比如處理連接、解析優化等;
  • 存儲引擎層,負責存儲相關的具體事宜。

redo日志是InnoDB存儲引擎特有的日志,而Server層也有自己的日志,稱為 binlog(歸檔日志),它可以被所有存儲引擎使用。

6.1 為什么有了redo日志還需要 binlog?

我想你可能會問出這個問題,實際上,更準確的問法是為什么有了binlog還需要有redo日志?主要有以下幾個原因。

  1. 因為最開始MySQL里并沒有InnoDB存儲引擎。MySQL自帶的引擎是MyISAM,但是 MyISAM沒有崩潰恢復的能力,InnoDB后來以插件的形式被引入,順便帶來了redo日志;
  2. binlog日志是用來歸檔的,binlog以事件的形式記錄了所有的 DDL和 DML 語句(因為它記錄的是操作而不是 數據值,屬于邏輯日志),但是不具備宕機恢復的功能,因為可能沒有來得及刷新臟頁,造成臟頁數據的丟失,而這些操作也沒有保存到binlog中從而造成數據丟失;
  3. binlog記錄的是關于一個事務的具體操作內容,即該日志是邏輯日志。而redo日志記錄的是關于每個頁的更改的物理情況。功能壓根不是一回事兒。

6.2 binlog日志的作用

6.2.1 主從復制

binlog是實現MySQL主從復制功能的核心組件。

master節點會將所有的寫操作記錄到binlog中,slave節點會有專門的I/O線程讀取master節點的binlog,將寫操作同步到當前所在的slave節點。

6.2.2 數據恢復

假如你在閱讀這篇文章的時候覺得我寫得實在太好,拍案叫絕的時候一不小心把公司的數據庫給刪了,你該怎么做才能恢復到你刪庫之前的那個時刻的狀態?

這個時候就要用到binlog了,前提是binlog沒有被刪除,否則,神仙也救不了你了。

通常情況下,公司會定期對數據庫進行全量備份,可能隔一個月,一周,甚至可能每天都備份一次。運氣好的話你可以使用前一天的全量備份,恢復到前一天的某時刻狀態(或者一周、一月之前),然后從全量備份的時刻開始,從binlog中提取該時刻之后(前提是你的binlog里面存放了這段時間的日志)的所有寫操作(當然,你得過濾掉你的刪庫操作),然后進行操作回放就可以了。

是不是很簡單?

問題又來了。再看一眼我們的更新語句。

update t_user_innodb set name ='chanmufeng1994' where id = 1;

假如這條更新語句已經被寫入到了redo日志,還沒來得及寫binlog的時候,MySQL宕機重啟了,我們看一下會發生什么。

因為redo日志可以在重啟的時候用于恢復數據,所以寫入磁盤的是chanmufeng1994。但是binlog里面沒有記錄這個邏輯日志,所以這時候用binlog去恢復數據或者同步到從庫,就會出現數據不一致的情況。

所以在寫兩個日志的情況下,就類似于「分布式事務」的情況,如果你不清楚分布式事務是個什么東西也沒關系,我在之后的文章會介紹到。能夠明確的就是redo日志和binlog日志如果單純依次進行提交是無法保證兩種日志都寫成功或者都寫失敗的。

我們需要「兩階段提交」。

6.3 兩階段提交

兩階段提交不是MySQL的專利,兩階段提交是一種跨系統維持數據邏輯一致性的常見方案,尤其在分布式事務上,所以請讀者重點體會思想

我們把redo日志的提交分成兩步,兩步中redo日志的狀態分別是prepare和commit。步驟如下

  1. InnoDB存儲引擎將更改更新到內存中后,同時將這個更新操作記錄到redo日志里面,此時redo日志處于prepare狀態;
  2. 執行器生成這個操作的binlog,并將binlog刷盤;
  3. 執行器調用InnoDB的提交事務接口,InnoDB把剛剛寫入的redo日志改成commit狀態。至此,所有操作完成。

加上兩階段提交之后我們再來看一下SQL更新語句的執行流程。

7. SQL更新語句的執行總結——終版

  1. 客戶端發送更新命令到MySQL服務器,經過處理連接、解析優化等步驟;
  2. Server層向InnoDB存儲引擎要id=1的這條記錄;
  3. 存儲引擎先從緩存中查找這條記錄,有的話直接返回,沒有則從磁盤加載到緩存中然后返回;
  4. Server層執行器修改這條記錄的name字段值;
  5. 存儲引擎更新修改到內存中;
  6. 存儲引擎記錄redo日志,并將狀態設置為prepare狀態;
  7. 存儲引擎通知執行器,修改完畢,可以進行事務提交;
  8. Server先寫了個binlog;
  9. Server提交事務;
  10. 存儲引擎將redo日志中和當前事務相關的記錄狀態設置為commit狀態。

完!

分享到:
標簽:SQL
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定