MySQL InnoDB 實現高併發原理

MySQL 原理篇

MySQL 索引機制

MySQL 體系結構及存儲引擎

MySQL 語句執行過程詳解

MySQL 執行計劃詳解

MySQL InnoDB 緩衝池

MySQL InnoDB 事務

MySQL InnoDB 鎖

MySQL InnoDB MVCC

MySQL InnoDB 實現高併發原理

MySQL InnoDB 快照讀在RR和RC下有何差異

轉載:《InnoDB併發如此高,原因竟然在這?》

併發控制

爲啥要進行併發控制?

併發的任務對同一個臨界資源進行操作,如果不採取措施,可能導致不一致,故必須進行併發控制(Concurrency Control)。

技術上,通常如何進行併發控制?

通過併發控制保證數據一致性的常見手段有:

  • 鎖(Locking)
  • 數據多版本(Multi Versioning)

鎖 

如何使用普通鎖保證一致性?

  1. 操作數據前,鎖住,實施互斥,不允許其他的併發任務操作;
  2. 操作完成後,釋放鎖,讓其他任務執行;

如此這般,來保證一致性。

普通鎖存在什麼問題? 

簡單的鎖住太過粗暴,連“讀任務”也無法並行,任務執行過程本質上是串行的。 

於是出現了共享鎖與排他鎖:

  • 共享鎖(Share Locks,記爲S鎖),讀取數據時加S鎖
  • 排他鎖(eXclusive Locks,記爲X鎖),修改數據時加X鎖

共享鎖與排他鎖的玩法是:

  • 共享鎖之間不互斥,簡記爲:讀讀可以並行
  • 排他鎖與任何鎖互斥,簡記爲:寫讀,寫寫不可以並行 

可以看到,一旦寫數據的任務沒有完成,數據是不能被其他任務讀取的,這對併發度有較大的影響。

有沒有可能,進一步提高併發呢?

即使寫任務沒有完成,其他讀任務也可能併發,這就引出了數據多版本。

數據多版本 

數據多版本是一種能夠進一步提高併發的方法,它的核心原理是:

  1. 寫任務發生時,將數據克隆一份,以版本號區分;
  2. 寫任務操作新克隆的數據,直至提交;
  3. 併發讀任務可以繼續讀取舊版本的數據,不至於阻塞;

 

如上圖: 

  1. 最開始數據的版本是V0;
  2. T1時刻發起了一個寫任務,這是把數據clone了一份,進行修改,版本變爲V1,但任務還未完成;
  3. T2時刻併發了一個讀任務,依然可以讀V0版本的數據;
  4. T3時刻又併發了一個讀任務,依然不會阻塞; 

可以看到,數據多版本,通過“讀取舊版本數據”能夠極大提高任務的併發度。

提高併發的演進思路,就在如此:

  • 普通鎖,本質是串行執行
  • 讀寫鎖,可以實現讀讀併發
  • 數據多版本,可以實現讀寫併發 

好,對應到InnoDB上,具體是怎麼玩的呢? 

redo、undo、回滾段 

在進一步介紹 InnoDB 如何使用“讀取舊版本數據”極大提高任務的併發度之前,有必要先介紹下 redo 日誌,undo 日誌,回滾段(rollback segment)。

爲什麼要有redo日誌?

數據庫事務提交後,必須將更新後的數據刷到磁盤上,以保證 ACID 特性。磁盤隨機寫性能較低,如果每次都刷盤,會極大影響數據庫的吞吐量。

優化方式是,將修改行爲先寫到 redo 日誌裏(此時變成了順序寫),再定期將數據刷到磁盤上,這樣能極大提高性能。

這裏的架構設計方法是,隨機寫優化爲順序寫,思路更重要。

假如某一時刻,數據庫崩潰,還沒來得及刷盤的數據,在數據庫重啓後,會重做 redo 日誌裏的內容,以保證已提交事務對數據產生的影響都刷到磁盤上。

一句話,redo 日誌用於保障,已提交事務的 ACID 特性。

爲什麼要有undo日誌?

數據庫事務未提交時,會將事務修改數據的鏡像(即修改前的舊版本)存放到 undo 日誌裏,當事務回滾時,或者數據庫奔潰時,可以利用 undo 日誌,即舊版本數據,撤銷未提交事務對數據庫產生的影響。

  • 對於 insert 操作,undo 日誌記錄新數據的 PK(ROW_ID),回滾時直接刪除;
  • 對於 delete/update 操作,undo 日誌記錄舊數據 row,回滾時直接恢復;
  • 他們分別存放在不同的 buffer 裏。

一句話,undo 日誌用於保障,未提交事務不會對數據庫的 ACID 特性產生影響。

什麼是回滾段? 

存儲 undo 日誌的地方,是回滾段。 

undo 日誌和回滾段和 InnoDB 的 MVCC 密切相關,這裏舉個例子展開說明一下。

表:t(id PK, name);

數據爲:

1, shenjian

2, zhangsan

3, lisi

此時沒有事務未提交,故回滾段是空的。

接着啓動了一個事務:

start trx;
delete (1, shenjian);
update set(3, lisi) to (3, xxx);
insert (4, wangwu);

可以看到:

  • 被刪除前的 (1, shenjian) 作爲舊版本數據,進入了回滾段;
  • 被修改前的 (3, lisi) 作爲舊版本數據,進入了回滾段;
  • 被插入的數據,PK(4) 進入了回滾段;

接下來,假如事務 rollback,此時可以通過回滾段裏的 undo 日誌回滾,假設事務提交,回滾段裏的 undo 日誌可以刪除。

可以看到:

  • 被刪除的舊數據恢復了;
  • 被修改的舊數據也恢復了;
  • 被插入的數據,刪除了;

事務回滾成功,一切如故。

InnoDB是基於多版本併發控制的存儲引擎

MVCC 就是通過“讀取舊版本數據”來降低併發事務的鎖衝突,提高任務的併發度。

核心問題:

  1. 舊版本數據存儲在哪裏?
  2. 存儲舊版本數據,對 MySQL 和 InnoDB 原有架構是否有巨大沖擊?

通過上文 undo 日誌和回滾段的鋪墊,這兩個問題就非常好回答了: 

  1. 舊版本數據存儲在回滾段裏;
  2. 對 MySQL 和 InnoDB 原有架構體系衝擊不大;

InnoDB 的內核,會對所有 row 數據增加三個內部屬性: 

  1. DB_TRX_ID,6字節,記錄每一行最近一次修改它的事務 ID;
  2. DB_ROLL_PTR,7字節,記錄指向回滾段 undo 日誌的指針;
  3. DB_ROW_ID,6字節,單調遞增的行 ID;

InnoDB 爲何能夠做到這麼高的併發?

回滾段裏的數據,其實是歷史數據的快照(snapshot),這些數據是不會被修改,select 可以肆無忌憚的併發讀取他們。

快照讀(Snapshot Read),這種一致性不加鎖的讀(Consistent Nonlocking Read),就是 InnoDB 併發如此之高的核心原因之一。

這裏的一致性是指,事務讀取到的數據,要麼是事務開始前就已經存在的數據(當然,是其他已提交事務產生的),要麼是事務自身插入或者修改的數據。 

什麼樣的 select 是快照讀?

除非顯示加鎖,普通的select語句都是快照讀,例如:

select * from t where id>2;

這裏的顯示加鎖,非快照讀是指:

select * from t where id>2 lock in share mode;
select * from t where id>2 for update;

總結 

  • 常見併發控制保證數據一致性的方法有鎖,數據多版本;
  • 普通鎖串行,讀寫鎖讀讀並行,數據多版本讀寫並行;
  • redo 日誌保證已提交事務的 ACID 特性,設計思路是,通過順序寫替代隨機寫,提高併發;
  • undo 日誌用來回滾未提交的事務,它存儲在回滾段裏;
  • InnoDB 是基於 MVCC 的存儲引擎,它利用了存儲在回滾段裏的 undo 日誌,即數據的舊版本,提高併發;
  • InnoDB 之所以併發高,快照讀不加鎖;
  • InnoDB 所有普通 select 都是快照讀;

本文的知識點均基於 MySQL5.6。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章