希望這篇文章能夠闡述清楚跟數據庫相關的四個概念:事務、數據庫讀現象、隔離級別、鎖機制
一、事務
先來看下百度百科對數據庫事務的定義:
作爲單個邏輯單元執行一系列操作,要麼完全執行,要麼完全不執行。事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。
事務有四個屬性,稱爲ACID屬性:
1、原子性(Atomicity):事務是一個原子單位,要麼全部執行,要麼全部不執行。
2、一致性(Consistent):事務的開始和結束,數據都必須保持一致狀態。
3、隔離性(isolation):數據庫系統提供隔離機制,保證併發事務之間是互相不干擾的。也就意味着事務處理過程中的中間狀態對其他的事務是透明的。
4、持久性(Durable):事務完成之後,對數據的修改是永久性的,即使出現系統故障也能夠保持
事務是一系列SQL語句的集合,如果沒有事務,會出現什麼問題?或者說SQL只能一條一條的單個執行,會出現什麼問題?
這個很簡單,如果沒有事務,我們平時生活中的銀行轉賬就無法操作。
二、數據庫讀現象
ACID屬性裏面有一個是隔離級別,即併發事務之間互相不干擾。互相不干擾只是一個終極狀態,且需要消耗巨大的性能。在我們實際應用過程中,是存在很大的灰度空間的:隔離級別有程度的區分。所以如果隔離程度控制的比較弱的話,就會產生髒讀、不可重複讀以及幻讀的現象。
1、髒讀
事務T1修改某個字段的值,然後事務T2讀取該值,此後T1撤銷了對該字段的更新,或者更新成另外的值才commit到數據庫中,這樣T2讀取的數據是無效的或者錯誤的。導致T2依據髒數據所做的操作也是錯誤的。
---------思聰同學中午去食堂吃飯,看到窗邊的座位被如花同學佔有了,思聰認爲這個座位已經被佔有了,就轉身去找其他的座位。不料,如花同學起身離開了。事實是:如花並不是吃飯,而是臨時坐在那裏等她的約會對象,只是臨時小坐一會,並沒有真正“commit”。
2、不可重複讀
在數據庫訪問中,一個事務範圍內的兩次相同的查詢卻返回了不同的數據。
事務T1讀取某一數據,事務T2讀取並修改了該數據,T1爲了對讀取值進行驗證而重新讀取,卻發現得到了不同的結果。
---------思聰同學中午去食堂吃飯,看到窗邊的座位是空的,便屁顛屁顛的跑去打飯,回來後卻發現這個座位被如花同學搶去了。
3、幻讀
幻讀解決了不可重複讀的問題,即在同一個事務範圍內,兩次相同的查詢結果是相同的。但是可以新增表中的數據記錄。
幻讀是指事務T1對錶中的數據進行修改,假設修改涉及了表中全部的數據行,同時第二個事務也修改這個表中的數據,這種修改是向表中插入一條新的數據。後面就會出現操作了T1事務的用戶發現表中還有沒有修改的數據行,彷彿出現了幻覺一樣。
--------思聰同學中午去食堂吃飯,看到窗邊的座位是空的,便屁顛屁顛的跑去打飯,回來後窗邊的座位還是空的,便很高興坐上去準備開始吃飯,這時候卻發現如花同學搬了一個小板凳坐在旁邊狼吞虎嚥,思聰頓時沒有了胃口。
如果需要解決髒讀、不可重複讀、幻讀等這些數據庫讀現象,就必須相應提高事務的隔離級別。但是數據庫的隔離級別越高,對應的併發能力就越弱,性能也就相應的越差,所以我們還需根據具體的應用場景去權衡。
三、事務隔離級別
1、未提交讀
事務的最低隔離級別,在這種隔離級別下,一個事務可以讀取另外一個事務未提交的數據。
數據庫鎖實現原理:
事務T在讀數據的時候並未對數據進行加鎖,事務T在修改數據的時候對數據增加行級共享鎖
T1在讀取數據時,T2可以對相同數據進行讀取、修改。因爲T1沒有進行任何鎖操作;當T2對記錄進行修改時,T1再次讀取數據可以讀取到T2修改後的數據。因爲T2對數據進行修改只增加了行級共享鎖,T1可以再增加共享讀鎖進行數據讀取(儘管T2沒有提交事務)
如上所述,這種隔離級別,會導致髒讀現象
在MySQL中的行級鎖,表級鎖,頁級鎖中介紹過,行級鎖是Mysql中鎖定粒度最細的一種鎖,行級鎖能大大減少數據庫操作的衝突。行級鎖分爲共享鎖和排他鎖兩種,本文將詳細介紹共享鎖及排他鎖的概念、使用方式及注意事項等。
共享鎖(Share Lock)
共享鎖又稱讀鎖,是讀取操作創建的鎖。其他用戶可以併發讀取數據,但任何事務都不能對數據進行修改(獲取數據上的排他鎖),直到已釋放所有共享鎖。
如果事務T對數據A加上共享鎖後,則其他事務只能對A再加共享鎖,不能加排他鎖。獲准共享鎖的事務只能讀數據,不能修改數據。
用法
SELECT ... LOCK IN SHARE MODE;
在查詢語句後面增加LOCK IN SHARE MODE
,Mysql會對查詢結果中的每行都加共享鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請共享鎖,否則會被阻塞。其他線程也可以讀取使用了共享鎖的表,而且這些線程讀取的是同一個版本的數據。
排他鎖(eXclusive Lock)
排他鎖又稱寫鎖,如果事務T對數據A加上排他鎖後,則其他事務不能再對A加任任何類型的封鎖。獲准排他鎖的事務既能讀數據,又能修改數據。
用法
SELECT ... FOR UPDATE;
在查詢語句後面增加FOR UPDATE
,Mysql會對查詢結果中的每行都加排他鎖,當沒有其他線程對查詢結果集中的任何一行使用排他鎖時,可以成功申請排他鎖,否則會被阻塞。
意向鎖
意向鎖是表級鎖,其設計目的主要是爲了在一個事務中揭示下一行將要被請求鎖的類型。InnoDB中的兩個表鎖:
意向共享鎖(IS):表示事務準備給數據行加入共享鎖,也就是說一個數據行加共享鎖前必須先取得該表的IS鎖
意向排他鎖(IX):類似上面,表示事務準備給數據行加入排他鎖,說明事務在一個數據行加排他鎖前必須先取得該表的IX鎖。
意向鎖是InnoDB自動加的,不需要用戶干預。
對於insert、update、delete,InnoDB會自動給涉及的數據加排他鎖(X);對於一般的Select語句,InnoDB不會加任何鎖,事務可以通過以下語句給顯示加共享鎖或排他鎖。
共享鎖:SELECT ... LOCK IN SHARE MODE;
排他鎖:SELECT ... FOR UPDATE;
2、已提交讀
在一個事務修改數據過程中,如果事務沒有進行提交,其他事務不能讀取該數據
事務T在讀取數據時增加行級共享鎖,讀取一旦結束,立即釋放;事務T在修改數據時增加行級排他鎖,直到事務結束才釋放。
T1在讀取數據的過程中,T2也可以對相同數據進行讀取,但是不能進行修改(T1增加的是共享鎖,T2也可以增加共享鎖,但是不能增加排他鎖)。T1讀取結束後,會立即釋放共享鎖,這時T2可以增加排他鎖,對數據進行修改,而此時T1既不能對數據進行讀取也不能進行修改,直到T2事務結束。
如上所述,這種隔離級別,解決了髒讀問題,但是不能解決不可重複讀現象。
3、可重複讀
事務T在數據讀取時,必須增加行級共享鎖,直到事務結束;事務T在修改數據過程中,必須增加行級排他鎖,直到數據結束。
T1在讀取數據的過程中,T2也可以對相同數據進行讀取,但是不能進行修改(T1增加的是共享鎖,T2也可以增加共享鎖,但是不能增加排他鎖)。直到T1事務結束後,纔會釋放共享鎖,這時T2纔可以增加排他鎖,對數據進行修改。
如上所述,這種隔離級別,解決了不可重複讀現象,但是這種隔離級別解決不了幻讀的問題:
T1進行查詢,讀取了10條記錄,並對十條記錄增加了行級鎖,此時T2是無法對這10行數據進行修改操作的,但是由於沒有表級鎖,它可以增加一條滿足T1查詢條件的記錄。隨後T1在進行查詢時,會發現雖然10條記錄沒有改變,但是突然多了一條記錄。
4、序列化
產生幻讀是由於沒有進行範圍查詢時沒有增加範圍鎖。
事務T在讀取數據時,必須先增加表級共享鎖,直到事務結束才釋放;事務T在修改數據時,必須先增加表級排他鎖,直到事務結束才釋放。
T1在讀取A表時,增加了表級共享鎖,此時T2也可以讀取A表,但是不能進行任何數據的修改,直到T1事務結束。隨後T2可以增加對A表的表級排他鎖,此時T1不能讀取A表中的任何數據,更不能進行修改。
如上所述,可序列化解決了髒讀、不可重複讀、幻讀等讀現象,但是隔離級別越來越高的同時,在併發性上也就越來越低。
四、事務操作實踐
默認情況下,MYSQL是自動提交的,也就意味着平時我們執行一條update語句時,MYSQL是自動幫我們提交的,儘快我們沒有顯示執行commit命令。但是這種只適用於單條SQL的執行。
如果我們想要同時執行多條SQL,並且執行過程中有SQL執行異常,需要回滾前面已經成功執行的SQL或者最終想回滾全部,則必須顯示的使用事務。
1、開始一項事務:start tr ansaction或者begin;
2、提交事務:commit;
3、回滾事務:rollback;
4、事務提交之後的操作:chain;
5、事務回滾之後的操作:release;
6、修改當前連接的提交方式:set autocommit;如果設置了set autocommit=0,則設置之後所有的事務都需要顯式的通過命令來進行提交或者回滾。
查詢當前會話的事務隔離級別
查詢當前系統的事務隔離級別
修改當前會話的事務隔離級別
提交讀演示
客戶端A 開啓事務,並更新數據
此時事務還沒有提交,開啓客戶端B,並進行查詢,此時的數據還是未更新前的
客戶端A進行事務提交,然後客戶端B查詢,此時是最新的數據
commit and chain的演示
如果在提交的時候使用commit and chain,那麼在提交後立即開始一個新的事務
A提交事務後,B再進行查詢
開啓事務會隱式解鎖
鎖表期間,用start transaction 命令開始一個新事務,則會隱式的執行unlock tables
A對錶進行寫鎖操作
此時B進行查詢:由於被A鎖表,所以查詢被阻塞
A開啓一個事務
由於A開啓事務,隱式的釋放了寫鎖,所以B的查詢不再被阻塞
SAVEPOINT的使用
事務中可以通過定義SAVEPOINT,指定回滾事務的一個部分A開啓事務並insert一條記錄,並設置savepoint
B進行查詢,查詢到的是開啓事務前的數據
A又插入一條數據,然後回滾到savepoint
B進行查詢