在數據庫的使用中,咱們經常把一系列操做的集合看做是一個獨立的單元,這種構成單一邏輯工做單元的集合被稱爲事務。sql
一個數據庫系統須要維護事務的如下四種特性,它們被合稱爲**"ACID",分別對應原子性**(Atomicity),一致性(Consistency),隔離性(Isolation),持久性(Durability)。數據庫
咱們用T來指定這個模型,它的SQL大體是如下形式:bash
-- 操做1:扣除A帳戶10元
UPDATE account SET amount = amount - 10 WHERE user_name = 'A'
-- 操做2:增長B帳戶10元
UPDATE account SET amount = amount + 10 WHERE user_name = 'B'
複製代碼
T是一個最簡單的轉帳模型,A用戶將10元轉到了B用戶的帳本上。服務器
以後咱們就用這個事務模型來解釋一下事務的四種特性。併發
原子性表示,一個事務所包含的操做集合是單一的,不可拆分的。並且事務中任何一個操做失敗,都要保證數據庫回滾到整個事務執行前的狀態。性能
在T事務中,有兩個操做,一個是在A帳戶扣除10元,一個是在B帳戶增長10元。這個兩個操做密不可分。ui
若是咱們將這兩個操做當作兩個獨立的事務,那麼假設初始狀態:spa
A帳戶:100元
B帳戶:100元
複製代碼
咱們如今執行了操做A,A帳戶餘額變爲90元。而後,而後!服務器室因爲某些不可控力,發生了爆炸。那麼最終結果將會變成:code
A帳戶:90元
B帳戶:100元
複製代碼
A和B就此開始了無止境的撕逼。。事務
B:你快給我轉錢啊!
A:我轉了啊!你看個人帳戶已經扣了10元了!
B:我這裏沒有收到啊!你本身看,仍是100元!
......
一致性原則要求事務的執行不改變數據庫的一致。即事務執行前若是數據庫一致,事務執行後,這種一致性仍然存在。
以T事務爲例,T執行前,A和B的帳戶餘額總和爲200元,那麼咱們要保證在T執行後,A和B的帳戶餘額總合仍然爲200元。
持久性的原則要求一旦事務成功完成執行,而且提交到數據庫,那麼這一次更新將會持久的。也就是說,只要事務成功執行,任何的系統故障都不能撤銷這一次事務的提交。
這個概念可能出現一些小漏洞,好比若是事務的結果儲存在內存中,那麼一旦宕機,全部的數據都會消失,咱們須要將數據提交到外部磁盤上,而且作好更新信息的記錄,使數據庫在宕機後重啓悽然能恢復到以前的狀態。因爲這個概念不屬於咱們的討論範圍,這裏也就再也不贅述。
隔離性確保事務併發執行後的系統狀態與這些事務以某種次序串行執行之後的狀態是等價的。
若是有多個事務併發執行,即便咱們確保事務的原子性和一致性,這些操做在執行時也並非嚴格的串行,而是以某種不可見的形式交叉執行,這種不可見行極可能會致使最終狀態的不一致。
舉個栗子,咱們將以前的事務T記做事務T1,並將T1中的操做細分,他們在系統中的實際操做應該大體是這樣的:
/**
* read(x):從數據庫中將x傳送到執行read操做的事務的主存緩衝區中
*
* write(x):從執行write的事務的主存緩衝區中將x取出並寫回數據庫(其實還有一個commit過程,這裏先忽略)
*/
read(A);
A := A-10;
write(A);
read(B);
B := B+10;
write(B);
複製代碼
除此之外,咱們再定義一個T2,計算A+B的值:
read(A);
read(B);
A := A+B;
複製代碼
並行的事務會如何執行呢?若是運氣好,它可能會按照T1,T2的順序完整執行,那麼最終咱們獲得的temp的狀態應該是200。
可是若是出現一種狀況,當T1中的A扣款成功,並切入數據庫,而在執行給B增長餘額的操做時,並無所有完成,而是執行完B := B+10
之後,開始執行T2,雖然B變量確實發生了改變,可是它尚未被寫進數據庫中,因此T2中計算出的temp變成了90+100=190。
大體流程會是這個樣子:
-- T1
read(A):
A := A-10;
write(A); --這裏A在數據庫中的值變成了90
read(B);
B := B+10; --這裏B確實發生了改變,可是並未提交至數據庫
-- T2
read(A); --A = 90
read(B); --B = 100(不是110)
temp := A+B; --獲得190
--T1
write(B) --這裏B的修改被提交到數據庫
複製代碼
爲了確保隔離性,數據庫系統中存在一種併發控制系統,來完成這一職責。
在介紹事務的隔離級別前,先來介紹一下髒讀,幻讀,不可重複讀的概念。
-- T1
read(A);
A := A+1;
write(A);
--T2
read(A);
A := A+2;
write(A);
commit;
-- T1
commit;
複製代碼
-- 假設初始狀態,A=10
-- T1
read(A); -- A = 10;
-- T2
read(A);
A = A+10;
write(A);
commit; -- A = 20;
-- T1
read(A); -- A = 20,與第一次不一樣
複製代碼
-- T1
UPDATE users SET status = 1;
-- T2
insert users (`status`) values ('0')
複製代碼
而後執行T1操做的用戶驚奇的發現,明明把全部的user狀態都置1了啊,怎麼還有一個0 ??????
可串行化(Serializable):sql中最高的隔離性級別,可以避免髒讀,幻讀,不可重複讀。代價也相對沉重,會大大影響數據庫的性能。
可重複讀(Repeatable read):只容許讀取已提交的數據,並且在一個事務兩次讀取一個數據項期間,其餘事務不得更新該數據。這種狀態不能避免幻讀。
已提交讀(Read committed):只容許讀取已提交數據,但不要求可重複讀。這種狀態只能避免髒讀。
未提交讀(Read uncommitted):容許讀取未提交的數據。這是最低的隔離級別,髒讀,幻讀,不可重複讀都沒法避免。
Attention:全部的隔離級別都不容許髒寫,即若是一個數據項已經被另一個還沒有提交或終止的事務寫入,則不容許其餘事務對它進行寫入。