事務(wù)
一. 事務(wù)的特性(ACID)
- 原子性(Atomicity): 事務(wù)是最小的執(zhí)行單位吐根,不允許分割赔蒲。事務(wù)的原子性確保動(dòng)作要么全部完成忧额,要么全部失敗回滾厘肮;
拿轉(zhuǎn)賬來說,假設(shè)用戶A給用戶B轉(zhuǎn)賬1000元睦番,那么A的余額減少1000类茂,B的余額增加1000耍属,如果一個(gè)出錯(cuò),所有都應(yīng)該失敗巩检。 - 一致性(Consistency): 執(zhí)行事務(wù)前后恬涧,數(shù)據(jù)保持一致,多個(gè)事務(wù)對(duì)同一個(gè)數(shù)據(jù)讀取的結(jié)果是相同的碴巾;
拿轉(zhuǎn)賬來說溯捆,假設(shè)用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉(zhuǎn)賬厦瓢,轉(zhuǎn)幾次賬提揍,事務(wù)結(jié)束后兩個(gè)用戶的錢相加起來應(yīng)該還得是5000,這就是事務(wù)的一致性煮仇。 - 隔離性(Isolation): 并發(fā)訪問數(shù)據(jù)庫(kù)時(shí)劳跃,一個(gè)用戶的事務(wù)不被其他事務(wù)所干擾,各并發(fā)事務(wù)之間數(shù)據(jù)庫(kù)是獨(dú)立的浙垫;
對(duì)于任意兩個(gè)并發(fā)的事務(wù)T1和T2刨仑,在事務(wù)T1看來,T2要么在T1開始之前就已經(jīng)結(jié)束夹姥,要么在T1結(jié)束之后才開始杉武,這樣每個(gè)事務(wù)都感覺不到有其他事務(wù)在并發(fā)地執(zhí)行。 - 持久性(Durability): 一個(gè)事務(wù)被提交之后辙售。它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變是持久的轻抱,即使數(shù)據(jù)庫(kù)發(fā)生故障也不應(yīng)該對(duì)其有任何影響。
二. 并發(fā)事務(wù)存在的問題
- 臟讀
臟讀是指在一個(gè)事務(wù)處理過程里讀取了另一個(gè)未提交的事務(wù)中的數(shù)據(jù)旦部。
當(dāng)一個(gè)事務(wù)正在多次修改某個(gè)數(shù)據(jù)祈搜,而在這個(gè)事務(wù)中這多次的修改都還未提交,這時(shí)一個(gè)并發(fā)的事務(wù)來訪問該數(shù)據(jù)士八,就會(huì)造成兩個(gè)事務(wù)得到的數(shù)據(jù)不一致容燕。
例如:用戶A向用戶B轉(zhuǎn)賬100元,對(duì)應(yīng)SQL命令如下
update account set money=money+100 where name=’B’; (此時(shí)A通知B)
update account set money=money - 100 where name=’A’;
當(dāng)只執(zhí)行第一條SQL時(shí)婚度,A通知B查看賬戶蘸秘,B發(fā)現(xiàn)確實(shí)錢已到賬(此時(shí)即發(fā)生了臟讀),而之后無(wú)論第二條SQL是否執(zhí)行陕见,只要該事務(wù)不提交秘血,則所有操作都將回滾,那么當(dāng)B以后再次查看賬戶時(shí)就會(huì)發(fā)現(xiàn)錢其實(shí)并沒有轉(zhuǎn)评甜。 - 不可重復(fù)讀
不可重復(fù)讀是指在對(duì)于數(shù)據(jù)庫(kù)中的某個(gè)數(shù)據(jù)灰粮,一個(gè)事務(wù)范圍內(nèi)多次查詢卻返回了不同的數(shù)據(jù)值,這是由于在查詢間隔忍坷,被另一個(gè)事務(wù)修改并提交了粘舟。
不可重復(fù)讀和臟讀的區(qū)別是熔脂,臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù)貌虾,而不可重復(fù)讀則是讀取了前一事務(wù)提交的數(shù)據(jù)郭计。 - 丟失修改
指在一個(gè)事務(wù)讀取一個(gè)數(shù)據(jù)時(shí),另外一個(gè)事務(wù)也訪問了該數(shù)據(jù)癣猾,那么在第一個(gè)事務(wù)中修改了這個(gè)數(shù)據(jù)后晰骑,第二個(gè)事務(wù)也修改了這個(gè)數(shù)據(jù)适秩。
例如:事務(wù)1讀取某表中的數(shù)據(jù)A=20,事務(wù)2也讀取A=20硕舆,事務(wù)1修改A=A-1秽荞,事務(wù)2也修改A=A-1,最終結(jié)果A=19抚官,事務(wù)1的修改被丟失扬跋。 - 幻讀
幻讀是事務(wù)非獨(dú)立執(zhí)行時(shí)發(fā)生的一種現(xiàn)象。例如事務(wù)T1對(duì)一個(gè)表中所有的行的某個(gè)數(shù)據(jù)項(xiàng)做了從“1”修改為“2”的操作凌节,這時(shí)事務(wù)T2又對(duì)這個(gè)表中插入了一行數(shù)據(jù)項(xiàng)钦听,而這個(gè)數(shù)據(jù)項(xiàng)的數(shù)值還是為“1”并且提交給數(shù)據(jù)庫(kù)。而操作事務(wù)T1的用戶如果再查看剛剛修改的數(shù)據(jù)倍奢,會(huì)發(fā)現(xiàn)還有一行沒有修改朴上,其實(shí)這行是從事務(wù)T2中添加的,就好像產(chǎn)生幻覺一樣娱挨,這就是發(fā)生了幻讀余指。
幻讀和不可重復(fù)讀都是讀取了另一條已經(jīng)提交的事務(wù)。不可重復(fù)讀的重點(diǎn)是修改跷坝,幻讀的重點(diǎn)在于新增或者刪除。
例1(同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來發(fā)現(xiàn)值不一樣了 ):事務(wù)1中的A先生讀取自己的工資為 1000的操作還沒完成碉碉,事務(wù)2中的B先生就修改了A的工資為2000柴钻,導(dǎo) 致A再讀自己的工資時(shí)工資變?yōu)?2000;這就是不可重復(fù)讀垢粮。
例2(同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣 ):假某工資單表中工資大于3000的有4人贴届,事務(wù)1讀取了所有工資大于3000的人,共查到4條記錄蜡吧,這時(shí)事務(wù)2 又插入了一條工資大于3000的記錄毫蚓,事務(wù)1再次讀取時(shí)查到的記錄就變?yōu)榱?條,這樣就導(dǎo)致了幻讀昔善。
三. 事務(wù)隔離級(jí)別
SQL 標(biāo)準(zhǔn)定義了四個(gè)隔離級(jí)別:
- READ-UNCOMMITTED(讀取未提交): 最低的隔離級(jí)別元潘,允許讀取尚未提交的數(shù)據(jù)變更,可能會(huì)導(dǎo)致臟讀君仆、幻讀或不可重復(fù)讀翩概。
- READ-COMMITTED(讀取已提交): 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù)牲距,可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生钥庇。
- REPEATABLE-READ(可重復(fù)讀): 對(duì)同一字段的多次讀取結(jié)果都是一致的牍鞠,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀评姨,但幻讀仍有可能發(fā)生难述。
- SERIALIZABLE(可串行化): 最高的隔離級(jí)別,完全服從ACID的隔離級(jí)別吐句。所有的事務(wù)依次逐個(gè)執(zhí)行胁后,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說蕴侧,該級(jí)別可以防止臟讀择同、不可重復(fù)讀以及幻讀。
MySQL InnoDB存儲(chǔ)引擎默認(rèn)支持的隔離級(jí)別是REPETABLE-READ(可重復(fù)讀)净宵。
與 SQL 標(biāo)準(zhǔn)不同的地方在于InnoDB 存儲(chǔ)引擎在 REPEATABLE-READ(可重讀)事務(wù)隔離級(jí)別下使用的是Next-Key Lock 鎖算法敲才,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫(kù)系統(tǒng)(如 SQL Server)是不同的择葡。所以說InnoDB 存儲(chǔ)引擎的默認(rèn)支持的隔離級(jí)別是 REPEATABLE-READ(可重讀) 已經(jīng)可以完全保證事務(wù)的隔離性要求紧武,即達(dá)到了 SQL標(biāo)準(zhǔn)的SERIALIZABLE(可串行化)隔離級(jí)別。
InnoDB 存儲(chǔ)引擎在 分布式事務(wù) 的情況下一般會(huì)用到SERIALIZABLE(可串行化)隔離級(jí)別敏储。
演示:
臟讀(讀取未提交)
讀取結(jié)果:
重復(fù)執(zhí)行第3步讀取結(jié)果:
讀取結(jié)果:
演示:
避免臟讀
讀取結(jié)果:
重復(fù)執(zhí)行第3步讀取結(jié)果:
重復(fù)執(zhí)行第3步讀取結(jié)果:
演示:
不可重復(fù)讀
(同避免臟讀)
演示:
可重復(fù)讀
讀取結(jié)果:
重復(fù)執(zhí)行第3步讀取結(jié)果:
表數(shù)據(jù):
演示:
防止幻讀
讀取結(jié)果:
重復(fù)執(zhí)行第3步執(zhí)行結(jié)果: