一次postgresql鎖事件,雖然知道如何解決了供炼,但是數(shù)據(jù)庫背后的原理是什么吼和?數(shù)據(jù)庫都有哪些鎖涨薪?為什么要有鎖?記得數(shù)據(jù)庫事務(wù)有ACID四大特性:原子性(事務(wù)是完整的操作要么成功要么回滾)炫乓、一致性(數(shù)據(jù)是一致的刚夺,銀行金額一致)、隔離性(并發(fā)事務(wù)修改數(shù)據(jù)是隔離的)和持久性(事務(wù)處理的結(jié)果是持久化的)末捣。相信你猜的出來鎖應(yīng)該和并發(fā)事務(wù)有關(guān)侠姑,但具體原因是什么呢?
并發(fā)事務(wù)的優(yōu)缺點
相對于串行處理來說箩做,并發(fā)事務(wù)處理能大大增加數(shù)據(jù)庫資源的利用率莽红,提高數(shù)據(jù)庫系統(tǒng)的事務(wù)吞吐量,從而可以支持更多的用戶邦邦。
并發(fā)事務(wù)處理帶來的4類問題:
更新丟失(Lost update) :兩個事務(wù)T1和T2讀入同一個數(shù)據(jù)并修改安吁,T2提交的結(jié)果覆蓋了T1提交的結(jié)果,導(dǎo)致T1的修改被丟失燃辖。
臟讀(Dirty Reads) :事務(wù)T1對數(shù)據(jù)進行修改鬼店,但是還沒有提交時,事務(wù)T2讀取數(shù)據(jù)進行修改黔龟,此時T2讀取的是T1修改了的值妇智。突然由于某種原因T1進行了回滾,這時候數(shù)據(jù)恢復(fù)了原來的值氏身,而T2取得的數(shù)據(jù)依然是T1修改的值巍棱,這就導(dǎo)致了數(shù)據(jù)庫中的值與事務(wù)獲取的值不同的現(xiàn)象,這就叫臟讀观谦。
不可重復(fù)讀(Non-repeatable Reads)? :在一個事務(wù)內(nèi)拉盾,多次讀取同一數(shù)據(jù)。在這個事務(wù)還沒結(jié)束時豁状,另外一個事務(wù)對該數(shù)據(jù)進行了修改捉偏。此時第一個事務(wù)再去讀此數(shù)據(jù)時讀到的結(jié)果與之前的結(jié)果不同倒得。在一個事務(wù)內(nèi)兩次相同的查詢讀到的數(shù)據(jù)是不一樣的,這就是不可重復(fù)讀夭禽。
幻讀(Phantom Reads) :目前工資為5000元的員工有10個人霞掺,事務(wù)A讀取所有工資為5000元的員工人數(shù)為10人。此時事務(wù)B插入一條工資也為5000的記錄讹躯。此時事務(wù)A再次讀取工資為5000元的員工菩彬,記錄為11人。此時產(chǎn)生了幻讀潮梯。
不可重復(fù)讀和幻讀的區(qū)別:前者的重點是修改:同樣條件下骗灶,你讀取過的數(shù)據(jù),再次讀取出來發(fā)現(xiàn)值不一樣了秉馏“业幻讀的重點在于新增或者刪除:同樣條件下,第一次和第二次讀出來的記錄數(shù)不一樣萝究。
“臟讀”免都、“不可重復(fù)讀”和“幻讀”,都是數(shù)據(jù)庫讀一致性問題帆竹,避免不一致的方法和技術(shù)就是進行并發(fā)控制绕娘,最常用的就是封鎖技術(shù)。
鎖技術(shù)
鎖是計算機協(xié)調(diào)多個進程或線程并發(fā)訪問某一資源的機制栽连。在數(shù)據(jù)庫中险领,除傳統(tǒng)的 計算資源(如CPU、RAM升酣、I/O等)的爭用外舷暮,數(shù)據(jù)也是一種供許多用戶共享的資源。如何保證數(shù)據(jù)并發(fā)訪問的一致性噩茄、有效性是所有數(shù)據(jù)庫必須解決的一 個問題下面,鎖沖突也是影響數(shù)據(jù)庫并發(fā)訪問性能的一個重要因素。
按鎖類型劃分绩聘,可分為共享鎖沥割、排他鎖
按鎖的粒度劃分,可分為表級鎖凿菩、行級鎖机杜、頁級鎖
按使用機制劃分,可分為樂觀鎖衅谷、悲觀鎖
共享鎖(也叫寫鎖椒拗、S鎖):多個事務(wù)可封鎖一個共享頁;任何事務(wù)都不能修改該頁;通常是該頁讀取完畢蚀苛,S鎖立即被釋放在验。在執(zhí)行select語句的時候需要給操作對象加上共享鎖,但加鎖之前需要檢查是否有排他鎖堵未,如果沒有腋舌,則可以加共享鎖(一個對象上可以加n個共享鎖)。共享鎖通常在執(zhí)行完select語句后被釋放渗蟹,當然也有可能是在事務(wù)結(jié)束(包括正常結(jié)束和異常結(jié)束)的時候被釋放块饺,主要取決于數(shù)據(jù)庫所設(shè)置的事務(wù)隔離級別。
排他鎖(也叫寫鎖雌芽、X鎖):僅允許一個事務(wù)封鎖此頁授艰;其他任何事務(wù)必須等到X鎖被釋放才能對該鎖頁進行訪問;X鎖一直到事務(wù)結(jié)束才能被釋放世落。執(zhí)行insert想诅、update、delete語句的時候需要給操作的對象加排他鎖岛心,在加排他鎖之前必須確認該對象上沒有其他任何鎖,一旦加上排他鎖之后篮灼,就不能再給這個對象加其他任何鎖忘古。排他鎖的釋放通常是在事務(wù)結(jié)束的時候(當然也有例外,就是在數(shù)據(jù)庫事務(wù)隔離級別被設(shè)置成Read Uncommitted(讀未提交數(shù)據(jù))的時候诅诱,這種情況下排他鎖會在執(zhí)行完更新操作之后被釋放髓堪,而不是在事務(wù)結(jié)束的時候)。
表級鎖:直接鎖定整張表娘荡,在鎖定期間干旁,其他進程無法對該表進行寫操作。如果是寫鎖炮沐,則其他進程讀也不允許争群。特點是:開銷小、加鎖快大年,不會出現(xiàn)死鎖换薄。鎖定粒度最大,發(fā)生鎖沖突的概率最高翔试,并發(fā)度最低轻要。
行級鎖:僅對指定的記錄進行加鎖,其他進程還可以對同一個表中的其他記錄進行操作垦缅。特點:開銷大冲泥,加鎖慢,會出現(xiàn)死鎖。鎖定的粒度最小凡恍,發(fā)生鎖沖突的概率最低志秃,并發(fā)度也最高。
頁級鎖:一次鎖定相鄰的一組記錄咳焚。開銷和加鎖時間介于表級鎖和行級鎖之間洽损;會出現(xiàn)死鎖;鎖定粒度也介于表級鎖和行級鎖之間革半,并發(fā)度一般碑定。
MySQL不同的存儲引擎支持不同的鎖機制。比如MyISAM和MEMORY存儲引擎采用的是表級鎖(table-level locking)又官;BDB存儲引擎采用的是頁面鎖(page-level locking)延刘,但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking)六敬,也支持表級鎖碘赖,但默認情況下是采用行級鎖。
悲觀鎖(Pessimistic Lock):當我們要對一個數(shù)據(jù)庫中的一條數(shù)據(jù)進行修改的時候外构,為了避免同時被其他人修改普泡,最好的辦法就是直接對該數(shù)據(jù)進行加鎖以防止并發(fā)。這種借助數(shù)據(jù)庫鎖機制审编,在修改數(shù)據(jù)之前先鎖定撼班,再修改的方式被稱之為悲觀并發(fā)控制。悲觀鎖主要是共享鎖或排他鎖垒酬。
樂觀鎖( Optimistic Locking ):相對悲觀鎖而言的砰嘁,樂觀鎖假設(shè)數(shù)據(jù)一般情況下不會造成沖突,所以在數(shù)據(jù)進行提交更新的時候勘究,才會正式對數(shù)據(jù)的沖突與否進行檢測矮湘,如果發(fā)現(xiàn)沖突了,則返回給用戶錯誤的信息口糕,讓用戶決定如何去做咸灿。
悲觀鎖的實現(xiàn)湿刽,往往依靠數(shù)據(jù)庫提供的鎖機制。在數(shù)據(jù)庫中,悲觀鎖的流程如下:
1) 在對記錄進行修改前包竹,先嘗試為該記錄加上排他鎖(exclusive locking)姻政。
2) 如果加鎖失敗浩考,說明該記錄正在被修改婶肩,那么當前查詢可能要等待或者拋出異常。具體響應(yīng)方式由開發(fā)者根據(jù)實際需要決定说搅。
3) 如果成功加鎖炸枣,那么就可以對記錄做修改,事務(wù)完成后就會解鎖了。
4) 期間如果有其他對該記錄做修改或加排他鎖的操作适肠,都會等待我們解鎖或直接拋出異常霍衫。
要使用悲觀鎖,必須關(guān)閉MySQL數(shù)據(jù)庫的自動提交屬性侯养。MySQL默認使用autocommit模式敦跌,當我們執(zhí)行一個更新操作后,MySQL會立刻將結(jié)果進行提交逛揩。(sql語句:set autocommit=0)
樂觀鎖的實現(xiàn)主要是兩個步驟:沖突檢測和數(shù)據(jù)更新柠傍。其實現(xiàn)方式有一種比較典型的就是CAS(Compare and Swap)。CAS是項樂觀鎖技術(shù)辩稽,當多個線程嘗試使用CAS同時更新同一個變量時惧笛,只有其中一個線程能更新變量的值,而其它線程都失敗逞泄,失敗的線程并不會被掛起患整,而是被告知這次競爭中失敗,并可以再次嘗試喷众。
在更新之前各谚,先查詢一下庫存表中當前庫存數(shù)(quantity),然后在做update的時候到千,以庫存數(shù)作為一個修改條件嘲碧。當提交更新的時候,判斷數(shù)據(jù)庫表對應(yīng)記錄的當前庫存數(shù)與第一次取出來的庫存數(shù)進行比對父阻,如果數(shù)據(jù)庫表當前庫存數(shù)與第一次取出來的庫存數(shù)相等,則予以更新望抽,否則認為是過期數(shù)據(jù)加矛。
如何選擇?
1) 樂觀鎖并未真正加鎖煤篙,效率高斟览。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高辑奈,容易發(fā)生業(yè)務(wù)失敗苛茂。
2) 悲觀鎖依賴數(shù)據(jù)庫鎖,效率低鸠窗。更新失敗的概率比較低妓羊。
隨著互聯(lián)網(wǎng)三高架構(gòu)(高并發(fā)、高性能稍计、高可用)的提出躁绸,悲觀鎖已經(jīng)越來越少的被使用到生產(chǎn)環(huán)境中了,尤其是并發(fā)量比較大的業(yè)務(wù)場景。
數(shù)據(jù)庫事務(wù)的隔離級別
封鎖技術(shù)雖然會解決數(shù)據(jù)不一致性問題净刮,但也會造成死鎖和性能下降剥哑。為了兼顧并發(fā)效率與異常控制淹父,定義了4中隔離級別株婴。
1、Read uncommitted(讀未提交數(shù)據(jù)):如果一個事務(wù)已經(jīng)開始寫數(shù)據(jù)暑认,則另外一個事務(wù)則不允許同時寫操作困介,但允許讀此行數(shù)據(jù)。該隔離級別通過“排他寫鎖”實現(xiàn)穷吮。避免了更新丟失逻翁,卻可能出現(xiàn)臟讀、不可重復(fù)讀捡鱼、幻讀的情況八回。
2、Read committed(讀已提交數(shù)據(jù)):讀取數(shù)據(jù)的事務(wù)允許其他事務(wù)繼續(xù)訪問該行數(shù)據(jù)驾诈,但是未提交的寫事務(wù)將會禁止其他事務(wù)訪問該行缠诅。該隔離級別避免了臟讀,但是卻可能出現(xiàn)不可重復(fù)讀乍迄、幻讀的情況管引。
3、Repeatable read(可重復(fù)讀:讀不寫寫全禁):讀取數(shù)據(jù)的事務(wù)將會禁止寫事務(wù)但允許讀事務(wù)闯两,寫事務(wù)則禁止其他任何事務(wù)褥伴。避免了不可重復(fù)讀取和臟讀,但是有時可能出現(xiàn)幻讀漾狼。這種隔離級別通過“共享讀鎖”和“排他寫鎖”實現(xiàn)重慢。
4、Serializable(可串行化):提供嚴格的事務(wù)隔離逊躁。要求事務(wù)串行化執(zhí)行似踱,事務(wù)只能一個接著一個地執(zhí)行,但不能并發(fā)執(zhí)行稽煤。如果僅僅通過“行級鎖”是無法實現(xiàn)事務(wù)串行化的核芽,必須通過其他機制保證新插入的數(shù)據(jù)不會被剛執(zhí)行查詢操作的事務(wù)訪問到。串行化是最高的事務(wù)隔離級別酵熙,同時代價也花費最高轧简,性能很低,一般很少使用匾二,在該級別下吉懊,事務(wù)順序執(zhí)行庐橙,不僅可以避免臟讀、不可重復(fù)讀借嗽,還避免了幻像讀态鳖。
大多數(shù)數(shù)據(jù)庫的默認級別是讀已提交Read committed,比如Sql Server , Oracle恶导。
Mysql的默認隔離級別就是可重復(fù)讀浆竭。
對于多數(shù)應(yīng)用程序,優(yōu)先考慮把數(shù)據(jù)庫系統(tǒng)的隔離級別設(shè)為Read Committed惨寿。它能夠避免臟讀取邦泄,而且具有較好的并發(fā)性能。盡管它會導(dǎo)致不可重復(fù)讀裂垦、幻讀和第二類丟失更新這些并發(fā)問題顺囊,在可能出現(xiàn)這類問題的個別場合,可以由應(yīng)用程序采用悲觀鎖或樂觀鎖來控制蕉拢。
參考