並發、事務和鎖

  • 2019 年 10 月 3 日
  • 筆記

並發,在操作系統中,是指一個很短的時間段中有幾個程序都處於已啟動運行到運行完畢之間,並發程序之間有相互制約關係,直接制約體現為一個程序需要另一個程序的計算結果,間接制約體現為多個程序競爭同一資源,如處理機、緩衝區、數據等。在數據庫系統中,並發主要是指資源的爭用,當兩個進程同時在訪問或更新同一個數據時,產生資源的爭用,資源爭用會引起一系列的問題,比如數據不一致、查詢阻塞、死鎖等。

 一,併發模式

在數據庫系統中,當多個進程訪問同一資源時,默認情況下,SQL Server會通過各種類型的鎖來協調資源的訪問,確保在並發環境下數據保持一致的狀態。而鎖的作用範圍是在事務中,事務建立在併發模式下。併發模式控制當發生讀寫衝突時,數據應該如何處理以保證數據的一致性。注意,寫和寫之間永遠衝突。

1,樂觀併發模式

對於樂觀併發模式,SQL Server假設只有少量的衝突發生,默認的機制是使用快照技術,在寫進程完成修改數據之前,先把數據的行版本保存到tempdb中。由於數據的舊數據已經保存,讀進程可以直接讀取已經保存的行版本,而不會受到寫進程的影響。

樂觀並發使得讀寫進程不會相互阻塞,但是,這會導致一個潛在的問題,讀進程可能會讀取到老的數據。

2,悲觀併發模式

這是默認的併發模式,在悲觀併發模式下,SQL Server認為有大量的寫操作發生,並且寫操作會受到寫操作的影響。也就是說,悲觀併發模式對任何正在訪問的數據進行加鎖,以避免多個進程同時修改或讀取數據,造成數據的不一致。在默認的隔離級別下,讀和寫是相互阻塞的。

二,事務

不管是在悲觀模式下,還是在樂觀模式下,都會涉及到事務。事務在邏輯上是一個整體,是一個工作單元,具有4個特性:

  • 原子性:事務整體是一個工作單元,對數據的修改操作,要麼全部執行,要麼完全不執行,沒有第三種狀態。
  • 一致性:在一個事務執行之前和執行之後數據庫都必須處於邏輯上的一致性狀態,數據在不同的事務中是相同的。
  • 隔離性:並發執行的事務之間是相互隔離的,一個事務內部的狀態,對其他事務是不可見的。
  • 持久性:當系統發生故障時,持久性確保已提交事務的更新不會丟失,也就是說一旦一個事務提交,DBMS保證數據的改變是永久性的,持久性通過事務日誌來保證。

事務有兩種觸發方式,隱式事務和顯式事務。

1,隱式事務

默認情況下,單個語句自動觸發隱式事務,在語句執行時,事務自動開始;當語句執行成功,事務自動提交;當語句執行失敗,事務自動回滾。隱式事務只能包含一個語句,當語句執行成功之後,事務就自動提交了。隱式事務不能回滾,除非語句運行失敗。

2,顯式事務

使用begin tran命令開啟一個事務;使用commit tran提交一個事務;使用rollback tran回滾一個事務。在事務內,可以包含多個語句,這些語句作為一個整體,具有事務的ACID屬性。

三,事務的隔離級別

每一個事務都運行在一個特定的隔離級別內,該隔離級別是由會話(session)決定的。SQL Server定義的四個隔離級別,都是為了定義讀數據的行為:

  • READ UNCOMMITTED :允許臟讀、數據不可重複讀和數據範圍不可重複讀
  • READ COMMITTED:防止臟讀(讀取未提交的數據更新),允許數據不可重複讀和數據範圍不可重複讀
  • REPEATABLE READ:防止臟讀和數據不可重複讀,允許數據範圍不可重複讀
  • SERIALIZABLE:防止臟讀、數據不可重複讀和數據範圍不可重複讀

注意:隔離級別只能定義讀操作的行為,無法定義寫操作的行為,寫操作跟寫操作與讀操作都是互斥的。

1,臟讀

當事務運行在 READ UNCOMMITTED 隔離級別下時,有可能會讀取到其他事務未提交的數據更新。

  • 例如,事務A修改了數據,把數據由1修改2,但是還沒有提交,
  • 之後,另一個事務B讀取了該數據,結果是2,
  • 在事務B讀取數據之後,事務A回滾,那麼該數據的最終值是1。

出現臟讀的原因是讀操作不加共享鎖,讀操作不會被寫操作阻塞,使得讀操作可能會讀取到寫事務未提交的值。

2,數據不可重複讀

當事務運行在 READ COMMITTED 隔離級別下時,在同一個事務內,兩次讀取同一個數據,可能讀取到不同的結果,這就是數據不可重複讀現象。

  • 例如,事務A讀取了一個數據,值是1,事務未提交
  • 之後,事務B修改了該數據,把數據值由1修改為2,並提交事務。
  • 在事務B提交之後,事務A重新讀取該書,值為2

出現數據不可重複讀的原因是事務在讀取數據時申請了共享鎖,但是在語句執行完成之後,就立馬釋放了共享鎖。要想避免出現數據不可重複讀的現象,事務必須一直持有共享鎖,直到事務提交或回滾。

3,數據範圍不可重複讀

當使用where子句限定了數據範圍之後,事務在兩次執行相同的查詢,獲得的數據範圍不同,也就是說,在相同的數據範圍內,增加了新的數據。

  • 例如,事務A查詢Age在10和20之間的男生,共有7個,事務未提交
  • 事務B向數據表中插入一條新的數據,Age是15,事務提交
  • 事務A重新查詢Age在10和20之間的男生,共有8個

出現數據範圍不可重複讀的原因是事務只申請了共享鎖,沒有申請範圍鎖,這就導致其他寫事務可以向特定的數據範圍內新增數據。

四,鎖

鎖是一種機制,鎖施加在資源上,表示鎖定該資源。鎖的所有者是事務,事務的特性決定了鎖的特性。

1,鎖定的資源

事務可以在數據行、數據頁、鍵範圍、分區和表上加鎖,

鎖定的資源類型,按照粒度的級別,從低向高分別是:

  • RID:堆中的一行
  • KEY:聚集索引中的鍵
  • KEY Range:鍵範圍
  • PAGE:堆表的數據頁或B-Tree的索引頁
  • EXTENT:分區,連續的8個Page
  • HoBT:堆或B-Tree, 用於保護堆(沒有聚集索引的表)和堆中的B 樹
  • IDX:索引中的數據行
  • OBJECT:表(整個表)

2,鎖模式

共享鎖(S鎖):讀操作在讀取的數據上施加共享鎖,用於select子句中

互斥鎖(X鎖):寫操作在更新的數據上施加互斥鎖,一個數據上,只能施加一個互斥鎖,X鎖和任意鎖都是互斥的。

更新鎖(U鎖):U鎖和S鎖是兼容的,在寫操作中,數據的更新過程分為兩步,第一步是找到更新的數據,對於找到的數據施加U鎖;第二步是在真正執行數據更新時,把U鎖轉換為X鎖。

意向鎖(I鎖):意向鎖有 IS、IX和IU 三種類型,當事務申請低粒度鎖時,事務會在相同的對象上,依次申請高粒度資源的意向鎖,直到表上的意向鎖。例如,事務A申請在數據行R上施加X鎖,同時,事務A會在包含該數據行R的數據頁上申請IX鎖,同時申請表級的IX鎖,也就是說,在一行上申請X鎖,那麼該行所在的頁和表都會申請IX鎖。在這種情況下,其他事務就很容易探測到有事務在更新表中的數據,避免鎖住整個表。

鍵範圍鎖(Key-Range):鍵範圍鎖出現在SERIALIZABLE隔離級別,如果事務需要掃描一個範圍的數據,事務使用鍵範圍鎖,鎖定表中的特定範圍,避免其他事務向範圍中插入新的數據,鍵範圍鎖與特定的索引鍵關聯。

3,鎖持續的時間

X鎖持續到事務結束,S鎖的持續時間受到事務隔離級別的影響。

  • READ UNCOMMITTED :不申請S鎖
  • READ COMMITTED:申請S鎖,S鎖在讀取操作完成時就立馬釋放,S鎖持續的時間是語句執行的時間
  • REPEATABLE READ:申請S鎖,當事務結束之後,立馬釋放S鎖,S鎖持續的時間是事務執行的時間
  • SERIALIZABLE:申請S鎖,鎖定的資源是鍵範圍,當事務結束之後,立馬釋放S鎖,S鎖持續的時間是事務執行的時間

4,鎖的兼容性

自行百度

5,鎖升級

鎖升級是指鎖施加的資源的粒度增加,從低粒度升級到高粒度,這會使數據庫系統並發度降低,增加阻塞和死鎖的風險,但是帶來的好處是降低鎖管理的開銷、保證查詢執行的速度。

鎖升級的第一種方式是鎖佔用的內存達到閾值。通常來說,一個鎖結構大概需要96B的內存空間,當數據庫中存在大量的鎖結構時,鎖結構會佔用較多的內存空間。當SQL Server使用超過24%的Buffer Poll用於存儲鎖結構時,SQL Server 引擎會選擇一些正在持有的鎖的會話,把鎖升級到高層次的級別,使鎖鎖定的資源粒度變大,降低鎖的數量。

鎖升級的第二種方式是鎖的數量達到閾值。在一個表上,當一個會話申請的鎖的數量達到一定的閾值,閾值的默認值是5000,也就是說,當一個會話持有鎖的數量超過5000時,SQL Server就自動把鎖升級。

鎖升級是自動進行的,也可以使用LOCK HINT,手動控制鎖的粒度。