概述
事務(wù)是一組讀寫操作,這些操作被當(dāng)作一個獨立的工作單元被執(zhí)行丈咐,這些操作的執(zhí)行結(jié)果要么全部成功瑞眼,要么全部失敗,不允許部分成功棵逊、部分失敗的情況出現(xiàn)伤疙。
在數(shù)據(jù)庫中,事務(wù)的作用是為了保障數(shù)據(jù)的一致性,即:操作結(jié)果和期望的結(jié)果一致徒像,它有四個特性:原子性黍特、一致性、隔離性锯蛀、持久性灭衷。
實列
事務(wù)最典型的一個例子是轉(zhuǎn)賬,假設(shè)我們有一個賬戶表旁涤,表中有兩條記錄翔曲,一條是A賬戶的記錄其余額為100,另一條是B賬戶的記錄其余額為50劈愚。
這時瞳遍,A賬戶發(fā)起了一筆轉(zhuǎn)賬到B賬戶的請求,其金額為50菌羽;此時該轉(zhuǎn)賬操作便是一個事務(wù)掠械,它包含兩個寫操作,一個是A賬戶扣減50注祖,另一個是B賬戶增加50猾蒂;
這兩個操作是不可分割的,不能出現(xiàn)只執(zhí)行其中一個而不執(zhí)行另一個氓轰,也不能出現(xiàn)其中一個失敗另一個成功婚夫,否則執(zhí)行結(jié)果就和我們期望的結(jié)果不一致。
比如署鸡,A執(zhí)行成功案糙,B執(zhí)行失敗,那么結(jié)果是A賬戶余額為:50靴庆,B賬戶余額還是:50时捌,這樣就和我們業(yè)務(wù)上期望的結(jié)果A為:50、B為:100不一致了炉抒。
兩個操作對應(yīng)的數(shù)據(jù)庫Sql語句如下:
//扣減A賬戶金額
update account set balance=balance-50 where id="A";
//增加B賬戶金額
update account set balance=balance+50 where id="B";
問題
因此奢讨,為了保障事務(wù)的一致性,我們首先要保重的是事務(wù)中的操作要么都成功要么都失敗不能出現(xiàn)其它情況焰薄,而且當(dāng)其中某些操作失敗時拿诸,該事務(wù)中其它成功的操作需要被撤銷使數(shù)據(jù)恢復(fù)到事務(wù)執(zhí)行前的狀態(tài),
這便是我們碰到的第一個問題:需要保證事務(wù)的原子性塞茅。
其次亩码,因為數(shù)據(jù)操作都是先從磁盤加載到內(nèi)存,然后在內(nèi)存進行數(shù)據(jù)更新野瘦,最后同步到磁盤描沟,這就引發(fā)了操作執(zhí)行后數(shù)據(jù)持久性的問題飒泻,即:如何保證事務(wù)成功后重啟數(shù)據(jù)庫后數(shù)據(jù)狀態(tài)還是期望的狀態(tài)。
最后吏廉,因為現(xiàn)代處理器的架構(gòu)都是多核架構(gòu)泞遗,在支持多個事務(wù)并行處理的情況下,如何保證事務(wù)之間不相互影響席覆,這便是事務(wù)之間隔離性的問題史辙,比如說:同一時刻有兩個事務(wù)給B賬戶轉(zhuǎn)賬50,如果兩個事務(wù)在執(zhí)行過程中獲取的賬戶金額都是50娜睛,那么最后的結(jié)果便是100髓霞,而不是150,這就和期望的結(jié)果不一致了畦戒;
所以方库,為了保證數(shù)據(jù)的一致性,我們需要事務(wù)保證其操作執(zhí)行的原子性和隔離性以及操作結(jié)果即數(shù)據(jù)的持久性障斋。
方案
下面我們以最常用的數(shù)據(jù)庫Mysql為例纵潦,講解一下它是通過什么方式保證事務(wù)的原子性、隔離性以及持久性的垃环,從而實現(xiàn)數(shù)據(jù)的一致性邀层。
原子性
原子性所要解決的關(guān)鍵問題是事務(wù)執(zhí)行失敗時,已經(jīng)執(zhí)行成功的操作如何撤銷或者說如何將已經(jīng)被更改的數(shù)據(jù)恢復(fù)到該事務(wù)開始執(zhí)行前的狀態(tài)遂庄。
Mysql使用的是undo日志寥院,事務(wù)開始后,如果要修改某一條記錄涛目,Mysql會先將該記錄的原始值保存在undo日志中并同步到磁盤秸谢,接著才是修改記錄,最后提交事務(wù)霹肝。
比如估蹄,我們只執(zhí)行下面這條Sql語句:
update account set balance=balance-50 where id="A";
那么其事務(wù)實現(xiàn)過程大致如下:
1、開始事務(wù)A
2沫换、將balance原始值100存儲到undo日志中
3臭蚁、將balance修改為50
4、提交事務(wù)
在上面的事務(wù)的執(zhí)行過程中讯赏,如果事務(wù)執(zhí)行到第三步出現(xiàn)了異常垮兑,那么可以通過undo日志恢復(fù)內(nèi)存中的數(shù)據(jù),如果在這之前出現(xiàn)異常什么都不需要做因為日志數(shù)據(jù)和原始數(shù)據(jù)是一致的漱挎。
持久性
持久性所面臨的問題是如何將內(nèi)存中的數(shù)據(jù)高效地同步到磁盤甥角,如上面的例子最簡單的方法就是在第三步后第四步前,將balance寫入磁盤识樱,如下:
1、開始事務(wù)A
2、將balance原始值100存儲到undo日志中
3怜庸、將balance修改為50
4当犯、將balance數(shù)據(jù)寫入磁盤
5、提交事務(wù)
但是割疾,如果存在多條變更的數(shù)據(jù)會因磁盤尋址耗時導(dǎo)致數(shù)據(jù)庫處理事務(wù)的性能變低嚎卫,所以Mysql采用redo日志來順序記錄變更的數(shù)據(jù),這樣減少了不必要的尋址宏榕。
等到拓诸,事務(wù)提交后,Mysql會使異步將redo中的數(shù)據(jù)線程
隔離性
隔離性所面臨的問題是一個事務(wù)在執(zhí)行的過程中是否允許另一個事務(wù)讀取它正在修改的數(shù)據(jù)麻昼,如果不允許那么就得讓事務(wù)串行化奠支,這是最高的隔離級別,如果允許那么又有三種不同的隔離級別:讀未提交抚芦、讀已提交倍谜、可重復(fù)讀绍昂。
讀未提交:一個事務(wù)可以讀取另一個事務(wù)未提交的數(shù)據(jù)梳虽,這種不采取任何隔離措施的策略會導(dǎo)致臟讀殖演、幻讀锦秒、不可重讀等問題力崇,而且對數(shù)據(jù)一致性沒有任何保障瘾婿。
讀已提交:一個事務(wù)可以讀取另一個事務(wù)已經(jīng)提交的數(shù)據(jù)馒胆,它避免了臟讀热康,但還存在幻讀消返、不可重復(fù)讀的問題载弄。
可重復(fù)讀:同個事務(wù)多次讀取相同的數(shù)據(jù)放回的結(jié)果是一樣的,Mysql的可重復(fù)讀可以解決臟讀侦副、幻讀侦锯、不可重讀的問題。
那什么情況下會發(fā)生臟讀秦驯、幻讀尺碰、不可重讀呢?
臟讀發(fā)生在一個事務(wù)A讀取另一個事務(wù)B未提交的數(shù)據(jù)時译隘,如果B最后因為失敗回滾亲桥,那么A讀取的數(shù)據(jù)就是無效的。
幻讀發(fā)生在一個事務(wù)向數(shù)據(jù)庫插入某條數(shù)據(jù)ID為X的記錄是固耘,它先select沒有發(fā)現(xiàn)該記錄题篷,然后準(zhǔn)備插入此時發(fā)現(xiàn)數(shù)據(jù)庫已經(jīng)存在該條記錄,這條記錄是另一個事務(wù)在其select之后提交的厅目。
不可重復(fù)讀發(fā)生在一條記錄被并發(fā)修改番枚,這樣導(dǎo)致會導(dǎo)致某個事務(wù)多次讀取的數(shù)據(jù)結(jié)果不一樣法严。
擴展閱讀
架構(gòu)設(shè)計思維篇之結(jié)構(gòu)
架構(gòu)設(shè)計事務(wù)篇之Mysql事務(wù)原理