事務(wù)原理:
為了支持事務(wù),Innodb引入了下面幾個(gè)概念:
一、MYSQL 日志介紹:
1.binlog
binlog常用來進(jìn)行數(shù)據(jù)恢復(fù)、數(shù)據(jù)庫(kù)復(fù)制,常見的mysql主從架構(gòu),就是采用slave同步master的binlog實(shí)現(xiàn)的, 另外通過解析binlog能夠?qū)崿F(xiàn)mysql到其他數(shù)據(jù)源(如ElasticSearch)的數(shù)據(jù)復(fù)制。
2.redo log- 記錄新數(shù)據(jù)的備份,保證事務(wù)持久性
當(dāng)客戶端執(zhí)行每條SQL(更新語句)時(shí),redo log會(huì)被首先寫入log buffer;當(dāng)客戶端執(zhí)行COMMIT命令時(shí),log buffer中的內(nèi)容會(huì)被視情況刷新到磁盤。在事務(wù)提交前,只要將Redo Log持久化即可,不需要將數(shù)據(jù)持久化。當(dāng)系統(tǒng)崩潰時(shí),雖然數(shù)據(jù)沒有持久化,但是Redo Log已經(jīng)持久化。當(dāng)Mysql失效重啟進(jìn)行恢復(fù)時(shí)重新執(zhí)行redo log記錄的SQL進(jìn)行數(shù)據(jù)恢復(fù)。redo log在磁盤上作為一個(gè)獨(dú)立的文件存在,即Innodb的log文件。
3.undo log-保證事務(wù)的原子性? & 實(shí)現(xiàn)數(shù)據(jù)多版本MVCC
為了滿足事務(wù)的原子性,在操作任何數(shù)據(jù)之前,首先將數(shù)據(jù)備份到Undo Log.然后再進(jìn)行數(shù)據(jù)的修改。如果出現(xiàn)了錯(cuò)誤或者用戶執(zhí)行了ROLLBACK語句,系統(tǒng)可以利用Undo Log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài)。undo log用于數(shù)據(jù)的回滾操作。
? 具體操作是 copy事務(wù)前的數(shù)據(jù)行到undo buffer,在適合的時(shí)間(事務(wù)提交前)把undo buffer中的內(nèi)容刷新到磁盤。undo log記錄了數(shù)據(jù)變更歷史如,刪除前備份 insert插入undo log中原行記錄再在刪除行記錄上打刪除標(biāo)記,修改前備份 insert插入undo log中原行記錄再修改更新行數(shù)據(jù)打標(biāo)事務(wù)id&回滾指針(指向undo log中原行記錄)。通過undo log可以實(shí)現(xiàn)事務(wù)回滾,并且可以根據(jù)undo log回溯到某個(gè)特定的版本的數(shù)據(jù),實(shí)現(xiàn)MVCC。? 與redo log不同的是,磁盤上不存在單獨(dú)的undo log文件,所有的undo log均存放在主ibd數(shù)據(jù)文件中(表空間)。
二、mysql鎖-實(shí)現(xiàn)隔離性
Innodb提供了行鎖,分兩種:排他鎖、共享鎖。
共享鎖針對(duì)讀,排他鎖針對(duì)寫,完全等同讀寫鎖的概念。
排他鎖: 寫鎖, 如果某個(gè)事務(wù)在更新某行,則其他事物無論是讀還是寫本行都必須等待;
共享鎖: 讀鎖,如果某個(gè)事物讀某行,則其他讀的事物無需等待,而寫事物則需等待。
事務(wù)隔離級(jí)別 - 隔離級(jí)別依次增強(qiáng),但是導(dǎo)致的問題是并發(fā)能力的減弱
眾所周知地是更新(update、insert、delete)是一個(gè)事務(wù)過程,在Innodb中,查詢也是一個(gè)事務(wù),只讀事務(wù)。當(dāng)讀寫事務(wù)并發(fā)訪問同一行數(shù)據(jù)時(shí),能讀到什么樣的內(nèi)容則依賴事務(wù)級(jí)別:
- READ_UNCOMMITTED:讀未提交-臟讀- 事務(wù)能夠看到其他事務(wù)沒有提及的修改,當(dāng)另一個(gè)事務(wù)又回滾了修改后的情況又被稱為臟讀dirty read
- READ_COMMITTED:讀提交-幻讀- 事務(wù)能夠看到其他事務(wù)提交后的修改,會(huì)出現(xiàn)一個(gè)事務(wù)內(nèi)兩次讀取數(shù)據(jù)可能因?yàn)槠渌聞?wù)提交的修改導(dǎo)致不一致的情況,稱為不可重復(fù)讀。
- REPEATABLE_READ:重復(fù)讀 (默認(rèn)級(jí)別)-每次都讀取指定的版本,這樣保證不會(huì)產(chǎn)生幻讀,但可能讀不到最新的數(shù)據(jù)
- SERIALIZABLE:串行化 -鎖表,讀寫相互阻塞,使用較少
三、MYSQL事務(wù)原理:
Innodb ACID如何保證
? 原子性 redo + undo
? 一致性 redo
? 隔離性 鎖 + MVCC
? 持久性 redo
下面演示下事務(wù)對(duì)某行記錄的更新過程:
todo

F1~F6是某行列的名字,1~6是其對(duì)應(yīng)的數(shù)據(jù)。后面三個(gè)隱含字段分別對(duì)應(yīng)該行的事務(wù)號(hào)和回滾指針,假如這條數(shù)據(jù)是剛INSERT的,可以認(rèn)為ID為1,其他兩個(gè)字段為空。

當(dāng)事務(wù)1更改該行的值時(shí),會(huì)進(jìn)行如下操作:
用排他鎖鎖定該行
記錄redo log
把該行修改前的值Copy到undo log,即上圖中下面的行
修改當(dāng)前行的值,填寫事務(wù)編號(hào),使回滾指針指向undo log中的修改前的行
commit 提交事務(wù) / rollback回滾事務(wù)(需要根據(jù)當(dāng)前回滾指針從undo log中找出事務(wù)修改前的行記錄版本,并恢復(fù)。)
上述過程確切地說是描述了UPDATE的事務(wù)過程,Undo log分為Insert和Update兩種,delete可以看做是一種特殊的update,即在記錄上修改刪除標(biāo)記。

與事務(wù)1相同,此時(shí)undo log,中有有兩行記錄,并且通過回滾指針連在一起。todo delete
因此,如果undo log一直不刪除,則會(huì)通過當(dāng)前記錄的回滾指針回溯到該行創(chuàng)建時(shí)的初始內(nèi)容,所幸的時(shí)在Innodb中存在purge線程,它會(huì)查詢那些比現(xiàn)在最老的活動(dòng)事務(wù)還早的undo log,并刪除它們,從而保證undo log文件不至于無限增長(zhǎng)。todo
四、MYSQL到底是怎么實(shí)現(xiàn)MVCC的?
在Mysql中MVCC是在Innodb存儲(chǔ)引擎中得到支持的,Innodb為每行記錄都實(shí)現(xiàn)了三個(gè)隱藏字段:
6字節(jié)的事務(wù)ID(DB_TRX_ID)-標(biāo)識(shí)該行所屬的事務(wù)、
7字節(jié)的回滾指針(DB_ROLL_PTR)、
隱藏的ID
innodb存儲(chǔ)的最基本row中包含一些額外的存儲(chǔ)信息 DATA_TRX_ID(標(biāo)識(shí)該行所屬的事務(wù)),DATA_ROLL_PTR(回滾指針),DB_ROW_ID(隱藏id),DELETE BIT(刪除標(biāo)志)
6字節(jié)的DATA_TRX_ID 標(biāo)記了最新更新這條行記錄的transaction id,每處理一個(gè)事務(wù),其值自動(dòng)+1
7字節(jié)的DATA_ROLL_PTR 指向當(dāng)前記錄項(xiàng)的rollback segment的undo log記錄,找之前版本的數(shù)據(jù)就是通過這個(gè)指針
6字節(jié)的DB_ROW_ID,當(dāng)由innodb自動(dòng)產(chǎn)生聚集索引時(shí),聚集索引包括這個(gè)DB_ROW_ID的值,否則聚集索引中不包括這個(gè)值.,這個(gè)用于索引當(dāng)中
DELETE BIT位用于標(biāo)識(shí)該記錄是否被刪除,這里的不是真正的刪除數(shù)據(jù),而是標(biāo)志出來的刪除。真正意義的刪除是在commit的時(shí)候

MYSQL如何實(shí)現(xiàn)事務(wù)?
? MYSQL讀寫鎖:讀鎖和讀鎖之間不互斥,而寫鎖和寫鎖、讀鎖都互斥,提升并發(fā)讀能力。為了進(jìn)一步提升并發(fā)能力,實(shí)現(xiàn)讀寫之間也不沖突,讀取數(shù)據(jù)時(shí)通過一種類似快照的方式將數(shù)據(jù)保存下來,這樣讀鎖就和寫鎖不沖突了,不同的事務(wù)session會(huì)看到自己特定版本的數(shù)據(jù)。
MVCC原理:

? 歷史版本存儲(chǔ)在UNDO Log空間
? 每條記錄帶版本號(hào)及回滾指針
? 廢棄記錄異步purge
MVCC實(shí)現(xiàn)
新增一個(gè)事務(wù)時(shí)事務(wù)id會(huì)增加,trx_id能夠表示事務(wù)開始的先后順序。
update undo log記錄了數(shù)據(jù)之前的數(shù)據(jù)信息,通過這些信息可以還原到之前版本的狀態(tài)。
當(dāng)進(jìn)行插入操作時(shí),生成的Insert undo log在事務(wù)提交后即可刪除,因?yàn)槠渌聞?wù)不需要這個(gè)undo log。
進(jìn)行刪除修改操作時(shí),會(huì)生成對(duì)應(yīng)的undo log,并將當(dāng)前數(shù)據(jù)記錄中的db_roll_ptr指向新的undo log

---
MVCC概念: 提升事務(wù)并發(fā)處理能力
1、MVCC機(jī)制是行級(jí)鎖的一種妥協(xié),多線程事務(wù)讀取時(shí),避免使用鎖,而是采用一種更小的開銷,允許非阻塞讀取,寫操作進(jìn)行時(shí)只鎖定必要的記錄
2、簡(jiǎn)單的實(shí)現(xiàn)方式:MVCC保存某個(gè)時(shí)間點(diǎn)上的數(shù)據(jù)快照。一個(gè)事務(wù)內(nèi),看到的是同一個(gè)版本的快照,數(shù)據(jù)一致。不同事務(wù)在同一時(shí)間點(diǎn)看到的數(shù)據(jù)會(huì)不一致,因?yàn)樗麄兊玫降臄?shù)據(jù)版本不一樣。
MVCC可以任務(wù)它是行級(jí)鎖的一個(gè)變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。實(shí)現(xiàn)了非阻塞的讀操作提高db并發(fā)能力,寫操作也只鎖定必要的行。
MVCC的基本原理: 非理想態(tài)MVCC,提供讀的非阻塞
MVCC的實(shí)現(xiàn),通過保存數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)的快照來實(shí)現(xiàn)的。這意味著一個(gè)事務(wù)無論執(zhí)行多長(zhǎng)時(shí)間,在同一個(gè)事務(wù)里看到數(shù)據(jù)都是一致的。根據(jù)事務(wù)開始的時(shí)間不同,每個(gè)事務(wù)對(duì)同一張表同一個(gè)時(shí)刻看到的數(shù)據(jù)可能不同。
MVCC的基本特征:
每行數(shù)據(jù)都存在一個(gè)版本,每次數(shù)據(jù)更新時(shí)都更新該版本。
修改時(shí)Copy出當(dāng)前版本隨意修改,各個(gè)事務(wù)之間無干擾。
保存時(shí)比較版本號(hào),如果成功(commit),則覆蓋原記錄;失敗則放棄copy(rollback)
就是每行都有版本號(hào),保存時(shí)根據(jù)版本號(hào)決定是否成功,聽起來含有樂觀鎖的味道,而Innodb的實(shí)現(xiàn)方式是:
事務(wù)以排他鎖的形式修改原始數(shù)據(jù)
把修改前的數(shù)據(jù)存放于undo log,通過回滾指針與主數(shù)據(jù)關(guān)聯(lián)
修改成功(commit)啥都不做,失敗則恢復(fù)undo log中的數(shù)據(jù)(rollback)
----
InnoDB存儲(chǔ)引擎MVCC的實(shí)現(xiàn)策略:
通過在每一行數(shù)據(jù)后面保存兩個(gè)隱藏的列實(shí)現(xiàn):當(dāng)前行創(chuàng)建時(shí)的版本號(hào)和刪除時(shí)的版本號(hào)(可能為空)。這里的版本號(hào)并不是實(shí)際的時(shí)間值,而是系統(tǒng)版本號(hào)(可以理解為事務(wù)的ID)。每開始一個(gè)新的事務(wù),系統(tǒng)版本號(hào)都會(huì)自動(dòng)遞增。事務(wù)開始時(shí)刻的系統(tǒng)版本號(hào)會(huì)作為事務(wù)的版本號(hào),用來和查詢到的每行記錄的版本號(hào)進(jìn)行比較。
每個(gè)事務(wù)又有自己的版本號(hào),這樣事務(wù)內(nèi)執(zhí)行CRUD操作時(shí),就通過版本號(hào)的比較來達(dá)到數(shù)據(jù)版本控制的目的。具體做法見下面的示意圖。

MVCC具體的操作如下:
SELECT:InnoDB會(huì)根據(jù)以下兩個(gè)條件檢查[需同時(shí)滿足]每行記錄:
1)InnoDB只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(也就是,行的系統(tǒng)版本號(hào)小于或等于事務(wù)的系統(tǒng)版本號(hào)),這樣可以確保事務(wù)讀取的行,要么是在事務(wù)開始前已經(jīng)存在的,要么是事務(wù)自身插入或者修改過的。
2)行的刪除版本要么未定義,要么大于當(dāng)前事務(wù)版本號(hào)。這可以確保事務(wù)讀取到的行,在事務(wù)開始之前未被刪除。
INSERT:InnoDB為新插入的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為行版本號(hào)。
DELETE:InnoDB為刪除的每一行保存當(dāng)前系統(tǒng)版本號(hào)作為行刪除標(biāo)識(shí)。
UPDATE:InnoDB為插入一行新記錄,保存當(dāng)前系統(tǒng)版本號(hào)作為行版本號(hào),同時(shí)保存當(dāng)前系統(tǒng)的版本號(hào)為原來的行作為刪除標(biāo)識(shí)。
說明
insert操作時(shí) “創(chuàng)建時(shí)間”=DB_ROW_ID,這時(shí),“刪除時(shí)間 ”是未定義的;
update時(shí),復(fù)制新增行的“創(chuàng)建時(shí)間”=DB_ROW_ID,刪除時(shí)間未定義,舊數(shù)據(jù)行“創(chuàng)建時(shí)間”不變,刪除時(shí)間=該事務(wù)的DB_ROW_ID;
delete操作,相應(yīng)數(shù)據(jù)行的“創(chuàng)建時(shí)間”不變,刪除時(shí)間=該事務(wù)的DB_ROW_ID;
select操作對(duì)兩者都不修改,只讀相應(yīng)的數(shù)據(jù)
保存這兩個(gè)額外系統(tǒng)版本號(hào),使大多數(shù)操作都可以不用加鎖。這樣設(shè)計(jì)使得計(jì)數(shù)據(jù)操作很簡(jiǎn)單,性能很好,并且也能保證只會(huì)讀取到符合標(biāo)準(zhǔn)的行。不足之處是每行記錄都需要額外的存儲(chǔ)空間,需要做更多的行檢查工作,以及一些額外的維護(hù)工作。
MVCC只在REPEATABLE READ和READ COMMITED兩個(gè)隔離級(jí)別下工作,其它兩個(gè)隔離級(jí)別和MVCC不兼容。
---
參考:
http://blog.csdn.net/chen77716/article/details/6742128
https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/
http://www.cnblogs.com/chenpingzhao/p/5065316.html
http://zhangxiong0301.iteye.com/blog/2230193