前言
沒什么前言展辞,直接開始吧媳叨!
主要想從下面三個方面說一下事務(wù)隔離級別:
- 為什么會出現(xiàn)事務(wù)隔離級別蕉朵。
- 事務(wù)隔離級別的種類崔涂。
- 如何實現(xiàn)事務(wù)隔離級別
為什么會出現(xiàn)事務(wù)隔離級別?
事務(wù)隔離隔離級別始衅,顧名思義冷蚂,就是事務(wù)的隔離級別,主要服務(wù)對象就是事務(wù)汛闸。
事務(wù)在高并發(fā)的情況下會暴露很多問題蝙茶,主要有三種:
- 臟讀
一個事務(wù)讀取到另一個事務(wù)未提交的數(shù)據(jù),別的行未提交的數(shù)據(jù)也叫臟數(shù)據(jù)诸老,所以叫臟讀隆夯。- 不可重復(fù)讀
在同一個事務(wù)中多次讀取同一個數(shù)據(jù),出現(xiàn)數(shù)據(jù)不一致别伏,重復(fù)讀取出現(xiàn)問題蹄衷,所以叫不可重復(fù)讀。
不可重復(fù)度可以看做臟讀的升級版本厘肮,讀到了別的事務(wù)已提交的數(shù)據(jù)愧口。- 幻讀
在同一個事務(wù)中多次讀取一定范圍內(nèi)的數(shù)據(jù),出現(xiàn)數(shù)據(jù)行不一致的情況类茂,像是出現(xiàn)幻覺一樣耍属,所以叫幻讀。
幻讀在不可重復(fù)讀的基礎(chǔ)上再上升了一檔大咱,不但當前行數(shù)據(jù)出現(xiàn)了不一致恬涧,查找范圍內(nèi)也出現(xiàn)了不一致注益。
這里比較難區(qū)分的是不可重復(fù)讀和幻讀碴巾,記住它們兩個針對對象不一致即可。
- 不可重復(fù)讀針對的主要是單行內(nèi)的數(shù)據(jù)丑搔,比如在同一個事務(wù)中讀取id為1的name為a厦瓢,過一段時間再讀取id為1的name卻變?yōu)閎了,就是不可重復(fù)讀啤月。
- 幻讀針對的主要是一定范圍內(nèi)的數(shù)據(jù)煮仇,比如在同一個事務(wù)中讀取id大于1但小于10的數(shù)據(jù)只有三條數(shù)據(jù),過一段時間再讀取這個范圍卻變成四條數(shù)據(jù)谎仲,就是幻讀浙垫。
MySQL是一款支持并發(fā)的數(shù)據(jù)庫軟件,肯定要解決上面的問題的啊,所以就衍生出了事務(wù)隔離這種東西夹姥,針對不同的問題杉武,對事務(wù)隔離又分了級,就是事務(wù)隔離級別
事務(wù)隔離級別的種類
針對上面的三個問題辙售,數(shù)據(jù)庫中存在四種事務(wù)隔離級別
- 未提交讀(READ_UNCOMMITTED)
最低的事務(wù)隔離級別轻抱,臟讀都不能避免。- 已提交讀(READ_COMMITED)
在這個隔離級別下可以避免臟讀旦部,但不能避免不可重復(fù)讀祈搜。- 可重復(fù)讀(REPEATABLE_READ)
在這個隔離級別下可以避免不可重復(fù)讀,但不能避免幻讀士八。- 串行化(SERLALIZABLE)
最高的隔離級別容燕,可以避免并發(fā)產(chǎn)生的問題。
事務(wù)隔離級別 | 避免臟讀 | 避免不可重復(fù)讀 | 避免幻讀 |
---|---|---|---|
未提交讀 | × | × | × |
已提交讀 | √ | × | × |
可重復(fù)讀 | √ | √ | × |
串行化 | √ | √ | √ |
(在InnoDB中有點特殊婚度,可重復(fù)讀級別也能在一定程度上避免幻讀)
天下沒有免費的午餐缰趋,越高的隔離級別在進行操作時開銷越大,支持的并發(fā)越低陕见,為了針對不同的業(yè)務(wù)場景秘血,MySQL才會對其進行分級。
各隔離級別特性不需要硬記评甜,從中文命名上基本上都能看出來灰粮。
事務(wù)隔離級別如何實現(xiàn)
這里說的是InnoDB事務(wù)隔離級別的實現(xiàn),畢竟InnoDB是MySQL中支持事務(wù)最優(yōu)秀的存儲引擎
未提交讀
這個沒什么好說的忍坷,基本沒采取什么措施粘舟。不過支持的并發(fā)度最高,在不更新數(shù)據(jù)的時候用這個隔離級別是非常不錯的選擇佩研。
已提交讀和可重復(fù)讀
這兩個可以放在一塊說柑肴,因為實現(xiàn)的原理差不多。
有鎖經(jīng)驗的可能能想到旬薯,直接對查詢的數(shù)據(jù)行使用共享鎖不就可以防止臟讀和不可重復(fù)讀的問題了嗎晰骑?的確,直接對數(shù)據(jù)行使用行鎖能有效的避免臟讀和不可重復(fù)讀的問題绊序,但是這也就限制了其他想要修改這行數(shù)據(jù)的事務(wù)硕舆。
可能有些人想問了,你想避免臟讀和不可重復(fù)讀不鎖住行怎么行骤公?InnoDB就是想不鎖住行抚官,還把問題給解決了。
如何在不鎖住行的情況下還能避免臟讀呢阶捆?這就涉及到兩個概念:當前讀和快照讀凌节。這兩個可以這樣理解:
- 當前讀:就是上面上鎖的思想钦听,讀取數(shù)據(jù)時順便將數(shù)據(jù)行加鎖,使其它事務(wù)不能修改數(shù)據(jù)倍奢,自然不會產(chǎn)生臟讀和不可重復(fù)讀的情況彪见。
- 快照讀:和名字一樣,就是對數(shù)據(jù)進行進行一次快照讀取數(shù)據(jù)中的內(nèi)容娱挨。因為不需要加鎖余指,又叫非阻塞讀。
為了能支持更高的并發(fā)跷坝,已提交讀和可重復(fù)讀的隔離級別select只要沒有顯式加鎖(顯式加鎖:select * from TABLE lock in share mode)酵镜,采用的就是快照讀。只不過采用的策略不一樣.- 已提交讀每次讀取都到特定事務(wù)版本的數(shù)據(jù)柴钻,沒有提交的數(shù)據(jù)不會對這兒的數(shù)據(jù)造成影響淮韭,這樣避免了臟讀的發(fā)生。
- 可重復(fù)讀對特定事務(wù)版本的數(shù)據(jù)進行快照之后贴届,會將讀取到的數(shù)據(jù)快照保存在了另一個地方靠粪,讀取讀取過的數(shù)據(jù)就會從這個地方中讀取(不是口胡毫蚓,有點繞)占键。因為事務(wù)提交也不會對另存的地方造成影響,這樣就避免了不可重復(fù)讀的發(fā)生元潘。
(可以理解為對數(shù)據(jù)做了一個備份畔乙,備份與原數(shù)據(jù)沒有太大的關(guān)聯(lián),但注意的是不是將所有數(shù)據(jù)都做一個備份翩概,那樣需要耗費的空間太大牲距,只是將查詢過的數(shù)據(jù)行進行備份,比如查詢id為1的數(shù)據(jù)行钥庇,并不會對id為2的數(shù)據(jù)行進行備份)
快照的實現(xiàn)原理主要涉及以下幾個概念牍鞠,這里就不展開來講了。
- 數(shù)據(jù)行中的DB_TRX_ID评姨、DB_ROLL_PTR难述、DB_ROW_ID
- DB_TRX_ID:事務(wù)id,InnoDB中事務(wù)都有一個id参咙,這個id是遞增的龄广,和事務(wù)開啟的時間有關(guān)系硫眯。也就是說蕴侧,越早開始的事務(wù)這個id越小。
- DB_ROLL_PTR:回滾指針两入,記錄著事務(wù)開始的時候在undo日志的位置净宵。
- DB_ROW_ID:行號。
- undo日志
當我們對數(shù)據(jù)做了變更操作的時候,就會產(chǎn)生undo記錄择葡,undo記錄的集合就是undo日志紧武。
undo中記錄的都是更改之前的數(shù)據(jù),當我們需要提取事務(wù)開始之前的數(shù)據(jù)時敏储,就需要到undo日志中去查找阻星。- read view
read view是事務(wù)開啟時,當前所有事務(wù)的一個集合已添。
可重復(fù)讀級別下的幻讀的防止
InnoDB使用next-key鎖保證防止幻讀的產(chǎn)生妥箕。
next-key鎖可以看成由行鎖和Gap鎖兩部分組成。行鎖不用說了更舞,主要說下Gap鎖畦幢。
Gap鎖
又稱間隙鎖,顧名思義缆蝉,就是針對間隙的鎖宇葱。使用Gap鎖的時候是會對數(shù)據(jù)周圍的間隙鎖住。這個間隙與數(shù)據(jù)有關(guān)刊头。比如果數(shù)據(jù)中有:1黍瞧、3、5原杂、7雷逆、9五個數(shù)據(jù),那么就可以對下面幾個間隙進行鎖定:
(-oo, 1]污尉、(1, 3]膀哲、(3, 5]、(5, 7]被碗、(7, 9]某宪、(9, +oo]
比如你查詢3這個數(shù)據(jù)行,那么這個事務(wù)未提交前其他事務(wù)就不能添加大于1小于等于5的數(shù)據(jù)(假設(shè)這個數(shù)據(jù)不是主鍵或唯一索引锐朴,主鍵或唯一索引有些特別兴喂,待會說)。添加值為4的數(shù)據(jù)行就會被阻塞住焚志。
查詢主鍵和唯一索引會不會使用Gap鎖還得看情況衣迷,主要有兩種情況:
- 如果where條件全部命中,則不會用Gap鎖酱酬,只會加記錄鎖壶谒。
全部命中:精確查詢時所有記錄都存在。- 如果where部分命中或者全部不命中膳沽,則會加上Gap鎖汗菜。
這也是為了性能考慮让禀,加鎖總是得付出代價的,能不加就不加陨界。
為什么主鍵或唯一索引where條件全部命中可以不用加Gap鎖呢巡揍?
如果全部命中,InnoDB就會對數(shù)據(jù)加上記錄鎖菌瘪,這個事務(wù)提交前別的事務(wù)不能刪除這些行腮敌。因為是唯一索引,所以也不可能添加一樣的數(shù)據(jù)俏扩,所以使用記錄鎖完全滿足條件缀皱。
串行化
這個是最高的隔離級別,所有的select采用的都是當前讀动猬,都會對數(shù)據(jù)上行鎖啤斗。