在開發(fā)Web應(yīng)用時(shí),經(jīng)常會(huì)用到InnoDB事務(wù)的特性,在一些涉及到金錢的業(yè)務(wù)上,事務(wù)可以保證資金流水不出錯(cuò),事務(wù)可以分為很多種,有扁平事務(wù)、鏈?zhǔn)聞?wù)、分布式事務(wù)等,這里只討論最簡單,也最常用的扁平事務(wù),我們經(jīng)常會(huì)提到事務(wù)的ACID特性,以下是我對(duì)ACID的理解和總結(jié)。
- 原子性
- 一致性
- 隔離性
- 持久性
原子性
原子性指的是對(duì)于一系列增刪改查的操作,要么全部執(zhí)行,要么全部不執(zhí)行,事務(wù)可通過start transaction或者begin語句顯示的開啟,commit用于顯示的提交,例如:
start transaction
update `user` set name='haha' where uid=1
insert into `user` (name) values ('小馬')
commit
對(duì)于以上兩部操作,若成功則都成功,若由于某些原因第二條語句執(zhí)行出錯(cuò),則可以執(zhí)行rollback語句,這些一般是由程序語言來控制。
一致性
一致性和原子性有很多相似的地方,我的理解是,一致性的特點(diǎn)是依賴于原子性實(shí)現(xiàn)的,什么意思呢?比如說A給B匯款1000元,這個(gè)狀態(tài)分為兩步即:
- 從A的賬戶扣1000元
- 再往B的賬戶+1000元
這兩個(gè)步驟,對(duì)于事務(wù)的原子性來說,指要么都成功,要么都失敗,原子性關(guān)注的是狀態(tài),而一致性關(guān)注的是最終的金額是否一致,即1000元不會(huì)丟失,可能理解起來會(huì)有點(diǎn)歧義,它們之間有相似的地方,一致性依賴于原子性實(shí)現(xiàn)。參考 事務(wù)隔離級(jí)別淺析。
隔離性
隔離性指的是事務(wù)在提交之前,對(duì)其它事務(wù)是不可見的,每個(gè)事務(wù)對(duì)對(duì)象的操作相對(duì)其它事務(wù)都是相互分離的,這個(gè)特性依靠鎖機(jī)制來實(shí)現(xiàn)。
持久性
持久性指的是事務(wù)一旦提交,其結(jié)果是永久性的,即使發(fā)生宕機(jī)等故障,數(shù)據(jù)庫也能將數(shù)據(jù)恢復(fù)。
事務(wù)的隔離級(jí)別
在數(shù)據(jù)庫操作中,為了解決并發(fā)數(shù)據(jù)讀寫時(shí)數(shù)據(jù)的正確性問題,提出了事務(wù)的隔離級(jí)別。數(shù)據(jù)庫的鎖也是為了構(gòu)建這些隔離級(jí)別而存在的。下面是SQL標(biāo)準(zhǔn)定義的四個(gè)隔離級(jí)別,以及可能發(fā)生臟讀、不可重復(fù)讀、幻讀的可能性,隔離級(jí)別從上往下一次遞增。
| 隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| 未提交讀(Read uncommitted) | 可能 | 可能 | 可能 |
| 已提交讀(Read committed) | 不可能 | 可能 | 可能 |
| 可重復(fù)讀(Repeatable read) | 不可能 | 不可能 | 可能 |
| 可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
臟讀、不可重復(fù)讀、幻讀
要理解事務(wù)的隔離級(jí)別,首先要理解臟讀、不可重復(fù)讀和幻讀是什么意思,下面通過幾個(gè)例子來演示一下。
- 臟讀
| 事務(wù)A | 事務(wù)B |
|---|---|
START TRANSACTION |
START TRANSACTION |
update user set name='小馬' where uid=1 |
|
select * from user where uid=1 |
|
commit |
rollback |
在Read uncommitted隔離級(jí)別下,A事務(wù)和B事務(wù)同時(shí)開啟,B事務(wù)對(duì)uid=1的這行數(shù)據(jù)做了修改操作,沒有提交,但是在事務(wù)A里面已經(jīng)能被讀取到,而此時(shí)事務(wù)B執(zhí)行了rollback回滾操作,也就是說A讀到的是一行不存在的數(shù)據(jù),這種情況被稱為臟讀,只有在未提交讀的隔離級(jí)別下才會(huì)發(fā)生這種情況。
- 可重復(fù)讀
| 事務(wù)A | 事務(wù)B |
|---|---|
START TRANSACTION |
START TRANSACTION |
select * from user where uid = 1 |
... |
| ... | update user set name='小馬' where uid=1 |
| ... | commit |
select * from user where uid = 1 |
... |
不可重復(fù)讀指的是在一個(gè)事務(wù)中,執(zhí)行兩次查詢操作,兩次結(jié)果是不一樣的,稱為不可重復(fù)讀,可重復(fù)讀則相反,同一個(gè)事務(wù)內(nèi),兩次讀到的數(shù)據(jù)一致。根據(jù)上面的例子,事務(wù)A和事務(wù)B同時(shí)開啟,第一次A事務(wù)通過select語句查詢uid=1的這行數(shù)據(jù),這時(shí)候B修改了這行數(shù)據(jù)并且執(zhí)行了提交操作,第二次A事務(wù)再通過select語句查詢這行數(shù)據(jù)的時(shí)候,讀取到的結(jié)果就不一樣了,所以稱為不可重復(fù)讀,這種情況在Read committed隔離級(jí)別下存在,Repeatable read級(jí)別則實(shí)現(xiàn)了可重復(fù)讀。
- 幻讀
| 事務(wù)A | 事務(wù)B |
|---|---|
START TRANSACTION |
START TRANSACTION |
select count(*) from user |
... |
| ... | insert into user (name) values ('mike') |
| ... | commit |
select count(*) from user |
... |
幻讀和不可重復(fù)讀有一些相似的地方,不可重復(fù)讀針對(duì)的是修改刪除操作,這兩種操作可通過對(duì)數(shù)據(jù)行增加一個(gè)排它鎖來解決,但是insert操作不一樣,你沒有辦法鎖住一條尚未存在的數(shù)據(jù),理論上在Repeatable read隔離級(jí)別只解決了不可重復(fù)讀問題,沒有解決幻讀問題,RR級(jí)別也是innodb默認(rèn)的隔離級(jí)別,值得慶幸的是,innodb的RR級(jí)別通過MVCC多版本并發(fā)控制,解決了在RR級(jí)別下的幻讀問題,所以理論上Innodb是完全滿足事務(wù)的ACID屬性的,想詳細(xì)了解MVCC的同學(xué)可自行搜索引擎或者看書,本文由于篇幅的原因,暫不詳細(xì)展開。
總結(jié)
理解了上面說的這些概念,就很容易理解事務(wù)的隔離級(jí)別了,下面是對(duì)事務(wù)隔離級(jí)別的總結(jié):
- 未提交讀:允許臟讀,A事務(wù)可以讀到B事務(wù)未提交的數(shù)據(jù)
- 已提交讀:A事務(wù)只能讀到B事務(wù)已提交的數(shù)據(jù)
- 可重復(fù)讀:A事務(wù)對(duì)于同一行數(shù)據(jù),前后兩次讀到的數(shù)據(jù)一定是一樣的,即使B事務(wù)對(duì)它執(zhí)行了修改,且提交了,結(jié)果也不會(huì)變。
- 串行化:完全串行化的讀,每次讀都需要獲得表級(jí)共享鎖,讀寫相互都會(huì)阻塞