MySQL 原理篇數據庫
MySQL 索引機制架構
MySQL 體系結構及存儲引擎併發
MySQL 語句執行過程詳解app
MySQL 執行計劃詳解高併發
MySQL InnoDB 緩衝池性能
MySQL InnoDB 事務優化
MySQL InnoDB 鎖spa
MySQL InnoDB MVCC架構設計
MySQL InnoDB 實現高併發原理設計
MySQL InnoDB 快照讀在RR和RC下有何差別
轉載:《InnoDB併發如此高,緣由居然在這?》
併發控制
爲啥要進行併發控制?
併發的任務對同一個臨界資源進行操做,若是不採起措施,可能致使不一致,故必須進行併發控制(Concurrency Control)。
技術上,一般如何進行併發控制?
經過併發控制保證數據一致性的常見手段有:
- 鎖(Locking)
- 數據多版本(Multi Versioning)
鎖
如何使用普通鎖保證一致性?
- 操做數據前,鎖住,實施互斥,不容許其餘的併發任務操做;
- 操做完成後,釋放鎖,讓其餘任務執行;
如此這般,來保證一致性。
普通鎖存在什麼問題?
簡單的鎖住太過粗暴,連「讀任務」也沒法並行,任務執行過程本質上是串行的。
因而出現了共享鎖與排他鎖:
- 共享鎖(Share Locks,記爲S鎖),讀取數據時加S鎖
- 排他鎖(eXclusive Locks,記爲X鎖),修改數據時加X鎖
共享鎖與排他鎖的玩法是:
- 共享鎖之間不互斥,簡記爲:讀讀能夠並行
- 排他鎖與任何鎖互斥,簡記爲:寫讀,寫寫不能夠並行
能夠看到,一旦寫數據的任務沒有完成,數據是不能被其餘任務讀取的,這對併發度有較大的影響。
有沒有可能,進一步提升併發呢?
即便寫任務沒有完成,其餘讀任務也可能併發,這就引出了數據多版本。
數據多版本
數據多版本是一種可以進一步提升併發的方法,它的核心原理是:
- 寫任務發生時,將數據克隆一份,以版本號區分;
- 寫任務操做新克隆的數據,直至提交;
- 併發讀任務能夠繼續讀取舊版本的數據,不至於阻塞;
如上圖:
- 最開始數據的版本是V0;
- T1時刻發起了一個寫任務,這是把數據clone了一份,進行修改,版本變爲V1,但任務還未完成;
- T2時刻併發了一個讀任務,依然能夠讀V0版本的數據;
- 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 就是經過「讀取舊版本數據」來下降併發事務的鎖衝突,提升任務的併發度。
核心問題:
- 舊版本數據存儲在哪裏?
- 存儲舊版本數據,對 MySQL 和 InnoDB 原有架構是否有巨大沖擊?
經過上文 undo 日誌和回滾段的鋪墊,這兩個問題就很是好回答了:
- 舊版本數據存儲在回滾段裏;
- 對 MySQL 和 InnoDB 原有架構體系衝擊不大;
InnoDB 的內核,會對全部 row 數據增長三個內部屬性:
- DB_TRX_ID,6字節,記錄每一行最近一次修改它的事務 ID;
- DB_ROLL_PTR,7字節,記錄指向回滾段 undo 日誌的指針;
- 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。