事務(wù)的四大特性
- 原子性(atomicity)
我們經(jīng)常說,一個(gè)事務(wù)執(zhí)行失敗了,就得回滾,其實(shí)這就是事務(wù)的原子性,一個(gè)完整事務(wù),要么全部執(zhí)行成功,如果有一個(gè)或者多個(gè)失敗,那么就要回滾,其實(shí)這也是另一個(gè)特性即一致性的基礎(chǔ) - 一致性(consistency)
一致性,先舉個(gè)栗子,最容易理解的栗子,本來劉備有200元,關(guān)羽沒有錢,那么劉備給關(guān)羽轉(zhuǎn)賬100元,現(xiàn)在需要兩步執(zhí)行這個(gè)操作,先減去劉備賬戶里面的100元,第二步,給關(guān)羽賬號增加100元,那么這兩步算是一個(gè)事務(wù)。在事務(wù)發(fā)生前,關(guān)羽0 +劉備200=200元,事務(wù)結(jié)束后,關(guān)羽100+劉備100=200,數(shù)據(jù)沒有平白增加或者減少。而且不管這兄弟倆怎么轉(zhuǎn),這個(gè)總和不會發(fā)生變化,這就是事務(wù)的一致性。一致性就是說事務(wù)必須使用數(shù)據(jù)庫從一個(gè)一致性狀態(tài)變換到另一個(gè)一致性狀態(tài)。想想我們講的第一個(gè)原子性,如果一半成功,一半失敗而沒有回滾,還會有數(shù)據(jù)的一致性嗎? - 隔離性(isolation)
隔離性主要是針對并發(fā)訪問來講的,當(dāng)多個(gè)用戶修改數(shù)據(jù)表時(shí),數(shù)據(jù)庫為每一個(gè)用戶開啟的事務(wù),不能被其他的事務(wù)干擾,多個(gè)并發(fā)要互相隔離,即對于同一個(gè)資源,在同一個(gè)時(shí)間段只能有一個(gè)事務(wù)可以修改 - 持久性(durability)
持久性就是說一旦事務(wù)成功提交,那么對于數(shù)據(jù)庫來說,這種改變是持久的
事務(wù)的隔離級別
在mysql中,支持四種隔離級別,即ru,rc,rr,serializealbe,后面我們會一一講解,mysql默認(rèn)支持的的rr,
-
此處給大家推薦一款好用的數(shù)據(jù)庫管理工具,jetbrains出品的datagrip;好用哇哇!,下面的sql語句都是在datagrip中運(yùn)行的
image.png READ UNCOMMITTED(未提交讀)
-
ru是指在一個(gè)事務(wù)執(zhí)行未完成的時(shí)候,數(shù)據(jù)對其他事務(wù)也是可見的,而且讀取的是已經(jīng)更改但是還沒有commit的數(shù)據(jù),這種保證不了數(shù)據(jù)準(zhǔn)確的隔離級別幾乎是不用的。也正如此,所以它的性能是最優(yōu)的
- 創(chuàng)建相關(guān)的數(shù)據(jù)庫和數(shù)據(jù)表
create schema test;
use test;
CREATE TABLE `t` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`point` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
下面粘貼sql語句,注意在同一行的代碼先左后右執(zhí)行,是在兩個(gè)sql窗口執(zhí)行的,在執(zhí)行到第6行的時(shí)候發(fā)現(xiàn),console2開啟了事務(wù),但是還沒有commit,但是console1已經(jīng)查詢到未提交的結(jié)果了。這就是讀未提交
image.png -
READ COMMITTED(讀已提交)
大部分的數(shù)據(jù)庫默認(rèn)隔離級別就是rc,就是說一個(gè)事務(wù)只能看見已經(jīng)提交的修改結(jié)果,如果沒有提交,那么只能看見原來的結(jié)果,而不會看到修改未提交的結(jié)果,但是這會出現(xiàn)一個(gè)問題,什么問題呢?就是 在一個(gè)事務(wù)中,如果另一個(gè)事務(wù)修改了數(shù)據(jù)并且提交,那么在第一個(gè)事務(wù)中針對同一個(gè)查詢,就會查詢出來兩個(gè)不同的數(shù)據(jù),即不可重復(fù)讀
image.png -
新增一條如果沒有提交也是讀取不到的
image.png -
這個(gè)時(shí)候,如果兩個(gè)窗口同時(shí)對一條數(shù)據(jù)更改會發(fā)生什么情況呢?第二條更新命令會一直等第一條命令的提交,未提交就處于等待狀態(tài)
image.png
- REPEATABLE READ(可重復(fù)讀)
可重復(fù)讀是mysql默認(rèn)的隔離級別,rr解決了臟讀的問題,但是可能會出現(xiàn)幻讀,下面先來演示一下幻讀的實(shí)現(xiàn) -
看一下已經(jīng)解決了讀未提交的問題和不可重復(fù)讀的問題
image.png -
幻讀其實(shí)是解決不可重復(fù)讀的一個(gè)缺點(diǎn),為什么?首先事務(wù)1已經(jīng)執(zhí)行了一個(gè)插入操作,新增id3,但是為了可重復(fù)讀,事務(wù)2看不到新增的數(shù)據(jù),所以當(dāng)事務(wù)2增加id3的時(shí)候報(bào)錯(cuò),因?yàn)閕d3在數(shù)據(jù)表中已經(jīng)切切實(shí)實(shí)的存在了??芍貜?fù)讀,有點(diǎn)像把某一個(gè)時(shí)刻的數(shù)據(jù)作為快照寫入了緩存,在commit之前所有的讀取都是源自緩存,而非真實(shí)的表
image.png
SERIALIZABLE(可串行化)
其實(shí)我們可以先自己想一下,如何在解決重復(fù)讀的時(shí)候還能解決幻讀呢?是不是感覺有點(diǎn)不可能,既然不幻讀,那就實(shí)現(xiàn)不了可重復(fù)讀,然鵝,但是,前面的操作都是基于兩個(gè)事務(wù),但是如我們把兩個(gè)事務(wù)再關(guān)聯(lián)一下呢,是不是就可以解決了,這就是串行的意思,可串行化解決了臟讀,幻讀,可重復(fù)讀等問題,但是,勢必會影響效率,"可串行化"會在讀取的每一行數(shù)據(jù)上都加鎖,所以可能會導(dǎo)致大量的鎖等待和超時(shí)問題,所以在實(shí)際的生產(chǎn)環(huán)境中也很少會用到這個(gè)隔離級別,只有在非常需要確保數(shù)據(jù)的一致性切可以接受沒有并發(fā)的情況下,才會考慮使用這個(gè)隔離級別。
-
演示一下,先看一下目前的情況
image.png-
怎么實(shí)現(xiàn)串行化呢,說起來有點(diǎn)惡心,這次不是緩存快照了,讓你讀實(shí)時(shí)數(shù)據(jù),但是更新操作我給你停了。我讓你等到?jīng)]有事務(wù)了,或者其他事務(wù)都提交了,才讓你這個(gè)寫操作執(zhí)行,嗯,就是這樣。感覺有點(diǎn)不太高明
image.png
-
-
用了鎖,有人讀也上鎖,有人寫也上鎖,效率能沒有影響嗎,寫之前先看有沒有讀鎖,有讀鎖就等待。這種方式就是 簡單,粗暴
image.png - 綜合下來,還是看使用的業(yè)務(wù)場景選擇不同的隔離級別,個(gè)人感覺大部分業(yè)務(wù)還是用rc比較好。你覺得呢?
附sql腳本
console1.sql
use test;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#READ UNCOMMITTED(未提交讀)
start transaction ;
select * from t where id =1;
update t set point=50 where id =1;
commit ;
#READ COMMITTED(讀已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
select * from t where id =1;
start transaction ;
update t set point=80 where id =1;
insert into t values (null,200);
select * from t where id =2;
commit ;
#REPEATABLE READ(可重復(fù)讀)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ ;
select * from t ;
start transaction ;
update t set point=100 where id=1;
commit ;
start transaction ;
select * from t ;
insert into t values (null,300);
select * from t ;
commit
## SERIALIZABLE(可串行化)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
start transaction ;
select * from t;
insert into t values (null,123);
console2.sql
use test;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#READ UNCOMMITTED(未提交讀)
start transaction ;
select * from t where id =1;
select * from t where id =1;
commit ;
#READ COMMITTED(讀已提交)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
select * from t where id =1;
start transaction ;
update t set point=10 where id =1;
select * from t where id =1;
select * from t where id =2;
commit ;
#REPEATABLE READ(可重復(fù)讀)
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ ;
select * from t ;
start transaction ;
select * from t where id=1;
select * from t where id=1;
start transaction ;
select * from t ;
select * from t ;
insert into t values (3,300);
commit
## SERIALIZABLE(可串行化)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
start transaction ;
select * from t;
commit;









