若是有人問你「數據庫事務有哪些特性」?你可能會很快回答出原子性、一致性、隔離性、持久性即ACID特性。那麼你知道InnoDB如何保證這些事務特性的嗎?若是知道的話這篇文章就能夠直接跳過不看啦(#^.^#)算法
先說結論:sql
重作日誌 redo log 分爲兩部分:一部分是內存中的重作日誌緩衝(redo log buffer),是易丟失的;二部分是重作日誌文件(redo log file),是持久的。InnoDB經過Force Log at Commit機制來實現持久性,當commit時,必須先將事務的全部日誌寫到重作日誌文件進行持久化,待commit操做完成纔算完成。
InnoDB在下面狀況下會將重作日誌緩衝的內容寫入重作日誌文件:數據庫
爲了確保每第二天志都寫入重作日誌文件,在每次將日誌緩衝寫入重作日誌文件後,InnoDB存儲引擎都須要調用一次fsync(刷盤)操做。但這也不是絕對的。用戶能夠經過修改innodb_flush_log_at_trx_commoit參數來控制重作日誌刷新到磁盤的策略,這個能夠做爲大量事務提交時的優化點。緩存
fsync的效率取決於磁盤的性能,所以磁盤的性能決定了事務提交的性能,也就是數據庫的性能。因此若是有人問你如何優化Mysql數據庫的時候別忘了有硬件這一條,讓他們提高硬盤配置,換SSD固態硬盤
重作日誌都是以512字節進行存儲的,稱之爲重作日誌塊,與磁盤扇區大小一致,這意味着重作日誌的寫入能夠保證原子性,不須要doublewrite技術。它有如下3個特性:微信
爲了知足事務的原子性,在操做任何數據以前,首先將數據備份到一個地方(這個存儲數據備份的地方稱爲Undo Log),而後進行數據的修改。若是出現了錯誤或者用戶執行了 ROLLBACK語句,系統能夠利用Undo Log中的備份將數據恢復到事務開始以前的狀態。
undo log實現多版本併發控制(MVCC)來輔助保證事務的隔離性。session
回滾日誌不一樣於重作日誌,它是邏輯日誌,對數據庫的修改都邏輯的取消了。當事務回滾時,它實際上作的是與先前相反的工做。對於每一個INSERT,InnoDB存儲引擎都會完成一個DELETE;對於每一個UPDATE,InnoDB存儲引擎都會執行一個相反的UPDATE。併發
事務提交後並不能立刻刪除undo log,這是由於可能還有其餘事務須要經過undo log 來獲得行記錄以前的版本。故事務提交時將undo log 放入一個鏈表中,是否能夠刪除undo log 根據操做不一樣分如下2種狀況:性能
事務的隔離性的實現原理就是鎖,於是隔離性也能夠稱爲併發控制、鎖等。事務的隔離性要求每一個讀寫事務的對象對其餘事務的操做對象能互相分離。再者,好比操做緩衝池中的LRU列表,刪除,添加、移動LRU列表中的元素,爲了保證一致性那麼就要鎖的介入。優化
InnoDB主要有2種鎖:行級鎖,意向鎖ui
行級鎖:
行級鎖中,除了S和S兼容,其餘都不兼容。
意向鎖:
解釋一下意向鎖
The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.
意向鎖的主要用途是爲了表達某個事務正在鎖定一行或者將要鎖定一行數據。e.g:事務A要對一行記錄r進行上X鎖,那麼InnoDB會先申請表的IX鎖,再鎖定記錄r的X鎖。在事務A完成以前,事務B想要來個全表操做,此時直接在表級別的IX就告訴事務B須要等待而不須要在表上判斷每一行是否有鎖。意向排它鎖存在的價值在於節約InnoDB對於鎖的定位和處理性能。另外注意了,除了全表掃描之外意向鎖都不會阻塞。
InnoDB有三種行鎖的算法:
這裏主要講一下Next-Key Lock,利用Next-key Lock鎖定的不是單個值而是一個範圍,他的目的就是爲了阻止多個事務將記錄插入到同一範圍內從而致使幻讀。
注意了,若是走惟一索引,那麼Next-Key Lock會降級爲Record Lock,即僅鎖住索引自己,而不是範圍。也就是說Next-Key Lock前置條件爲事務隔離級別爲RR且查詢的索引走的非惟一索引、主鍵索引。
下面咱們用個例子詳細說一下。
首先創建一張表:
CREATE TABLE T (id int ,f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 insert into T SELECT 1,1; insert into T SELECT 3,1; insert into T SELECT 5,3; insert into T SELECT 7,6; insert into T SELECT 10,8;
事務A執行以下語句:
SELECT * FROM T WHERE f_id = 3 FOR UPDATE
這時SQL語句走非惟一索引,所以使用Next-Key Locking加鎖,而且有2個索引,其須要分別進行鎖定。
對於彙集索引,其僅對id等於5的索引加上Record Lock。而對於輔助索引,其加上Next-Key Lock,鎖定了範圍(1,3),特別須要注意的是,InnoDB存儲引擎還會對輔助索引下一個鍵值加上Gap Lock,即範圍(3.6)的鎖。
因此若是在新session中執行以下語句都會報錯[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
:
select * from T where id = 5 lock in share MODE -- 不能執行,由於事務A已經給id=5的值加上了X鎖,執行會被阻塞 INSERT INTO T SELECT 4,2 -- 不能執行,輔助索引的值爲2,在(1,3)的範圍內,執行阻塞 INSERT INTO T SELECT 6,5 -- 不能執行,gap鎖會鎖住(3,6)的範圍,執行阻塞
此時想象一下,事務A鎖定了f_id =5 的記錄, 正常會有個gap lock,鎖住(5,6),那麼若是沒有(5,6)的gap鎖,那麼用戶能夠插入索引 f_id 爲5的記錄,這樣事務A再次查詢就會返回一個不一樣的記錄,也就致使了幻讀的產生。
同理,若是咱們事務A執行的是select * from T where f_id = 10 FOR UPDATE
,在表裏查不到數據,可是基於Next-Key Lock會鎖住(8,+∞),咱們執行INSERT INTO T SELECT 6,11
是沒法插入成功的,這就從根本上解決了幻讀問題。
更多內容請關注公衆號:JAVA日知錄