知識點:事務&鎖&併發

併發影響

  • 丟失更新
    理解:覆蓋提交

  • 髒讀(未提交的依賴關係)
    定義:當第二個事務選擇其他事務正在更新的行時,會發生未提交的依賴關係問題。 第二個事務正在讀取的數據還沒有提交併且可能由更新此行的事務所更改。
    理解:AB事務同時進行,B讀到被A刪除的事務

  • 不可重複讀(不一致的分析)
    定義:當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。
    理解: update、delete。加鎖

  • 虛擬讀取
    定義:執行兩個相同的查詢但第二個查詢返回的行集合是不同的,此時就會發生虛擬讀取。 下面的示例顯示了何時會出現幻讀。
    理解:insert。serializable隔離

  • 由於行更新,導致讀取缺失和重複讀
    隔離級別低的情況下,出現。
    如READ UNCOMMITTED,出現缺失一個更新行或多次看到某更新行、缺失非更新目標的一行或多行
    區別:

  1. 髒讀&不可重複讀:第二個事務讀取的數據是否已進行了更改的事務提交。
  2. 虛讀&不可重複讀:是否可以通過鎖機制解決其產生的問題。

併發類型

  • 悲觀鎖
    保守加鎖,每次獲取數據時加鎖,完成後解鎖。
    適用於寫頻次高的場景。
    java中,synchronized思想是悲觀鎖。
  • 樂觀鎖
    適用於讀頻次高的場景。
    1. 版本標識
      數據表中加上版本號version字段,每次修改,version+1
      流程:

      select version  ...;
      update tablename set a='test', version=version+1 where id=#{id} and version=#{version};
      
    2. CAS
      compare and swap(比較與交換),無鎖算法。
      涉及3個操作數:需要讀寫的內存值 V,進行比較的值 A,擬寫入的新值 B。
      詮釋:當且僅當 V 的值等於 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。
      缺點:ABA問題

      1 ABA 問題
      如果一個變量V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,
      JDK 1.5 以後的 AtomicStampedReference 類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置爲給定的更新值。
      2 循環時間長開銷大
      自旋CAS(也就是不成功就一直循環執行直到成功) 如果長時間不成功,會給CPU帶來非常大的執行開銷。 如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
      3 只能保證一個共享變量的原子操作
      CAS 只對單個共享變量有效,當操作涉及跨多個共享變量時 CAS 無效。但是從 JDK 1.5開始,提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行 CAS 操作.所以我們可以使用鎖或者利用AtomicReference類把多個共享變量合併成一個共享變量來操作。

非阻塞同步:Non-blocking Synchronization,在沒有線程被阻塞的情況下實現變量的同步

synchronized的底層實現主要依靠 Lock-Free 的隊列,基本思路是 自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程衝突較少的情況下,可以獲得和CAS類似的性能;而線程衝突嚴重的情況下,性能遠高於CAS。

數據庫引擎中的隔離級別

事務隔離級別控制:

  • 讀取數據時是否佔用鎖以及所請求的鎖類型。
  • 佔用讀取鎖的時間。
  • 引用其他事務修改的行的讀取操作是否:
    1. 在該行上的排他鎖被釋放之前阻塞其他事務。
    2. 檢索在啓動語句或事務時存在的行的已提交版本。
    3. 讀取未提交的數據修改。

隔離級別:

未提交讀
隔離事務的最低級別,只能保證不讀取物理上損壞的數據。 在此級別上,允許髒讀,因此一個事務可能看見其他事務所做的尚未提交的更改。
已提交讀
允許事務讀取另一個事務以前讀取(未修改)的數據,而不必等待第一個事務完成。 數據庫引擎保留寫鎖(在所選數據上獲取)直到事務結束,但是一執行 SELECT 操作就釋放讀鎖。 這是數據庫引擎默認級別。
可重複讀
數據庫引擎保留在所選數據上獲取的讀鎖和寫鎖,直到事務結束。 但是,因爲不管理範圍鎖,可能發生虛擬讀取。
可序列化
隔離事務的最高級別,事務之間完全隔離。 數據庫引擎保留在所選數據上獲取的讀鎖和寫鎖,在事務結束時釋放它們。 SELECT 操作使用分範圍的 WHERE 子句時獲取範圍鎖,主要爲了避免虛擬讀取。

注意: 請求可序列化隔離級別時,DDL 操作和事務複製的表上可能會失敗。 這是因爲複製查詢使用的提示可能與可序列化隔離級別不兼容。

mysql
ql數據庫定義了四種隔離級別:
serializable:可避免髒讀、不可重複讀、虛讀情況的發生。
repeatable read:可以避免髒讀、不可重複讀情況的發生。
read committed:可以避免髒讀情況發生。
read uncommitted:最低級別,都會發生。

鎖粒度

鎖粒度是被封鎖目標的大小,封鎖粒度小則併發性高,但開銷大,封鎖粒度大則併發性低但開銷小

SQL Server支持的鎖粒度可以分爲爲行、頁、鍵、鍵範圍、索引、表或數據庫獲取鎖。

資源 描述
RID 行標識符。用於單獨鎖定表中的一行。
鍵 索引中的行鎖。用於保護可串行事務中的鍵範圍。
頁 8 千字節 (KB) 的數據頁或索引頁。
擴展盤區 相鄰的八個數據頁或索引頁構成的一組。
表 包括所有數據和索引在內的整個表。
DB 數據庫。

鎖模式

定義:數據庫引擎使用不同的鎖模式鎖定資源,這些鎖模式確定了併發事務訪問資源的方式。

共享 (S) 用於不更改或不更新數據的操作(只讀操作),如 SELECT 語句。
更新 (U) 用於可更新的資源中。防止當多個會話在讀取、鎖定以及隨後可能進行的資源更新時發生常見形式的死鎖。
排它 (X) 用於數據修改操作,例如 INSERT、UPDATE 或 DELETE。確保不會同時同一資源進行多重更新。
意向鎖 用於建立鎖的層次結構。意向鎖的類型爲:意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。
架構鎖 在執行依賴於表架構的操作時使用。架構鎖的類型爲:架構修改 (Sch-M) 和架構穩定性 (Sch-S)。
大容量更新 (BU) 向表中大容量複製數據並指定了 TABLOCK 提示時使用。

sqlserver 表

HOLDLOCK 持有共享鎖,直到整個事務完成,應該在被鎖對象不需要時立即釋放,等於SERIALIZABLE事務隔離級別
NOLOCK 語句執行時不發出共享鎖,允許髒讀 ,等於 READ UNCOMMITTED事務隔離級別
PAGLOCK 在使用一個表鎖的地方用多個頁鎖
READPAST 讓sql server跳過任何鎖定行,執行事務,適用於READ UNCOMMITTED事務隔離級別只跳過RID鎖,不跳過頁,區域和表鎖
ROWLOCK 強制使用行鎖
TABLOCKX 強制使用獨佔表級鎖,這個鎖在事務期間阻止任何其他事務使用這個表
UPLOCK 強制在讀表時使用更新而不用共享鎖

死鎖

在兩個或多個任務中,如果每個任務鎖定了其他任務試圖鎖定的資源,此時會造成這些任務永久阻塞,從而出現死鎖。 例如:
事務 A 獲取了行 1 的共享鎖。
事務 B 獲取了行 2 的共享鎖。
現在,事務 A 請求行 2 的排他鎖,但在事務 B 完成並釋放其對行 2 持有的共享鎖之前被阻塞。
現在,事務 B 請求行 1 的排他鎖,但在事務 A 完成並釋放其對行 1 持有的共享鎖之前被阻塞。
事務 A 才能完成事務 B 完成,但是事務 B 由事務 A 阻塞此條件也稱爲循環依賴關係:事務 A 依賴於事務 B,此外,事務 B 由事務 A.上具有依賴關係關閉循環
除非某個外部進程斷開死鎖,否則死鎖中的兩個事務都將無限期等待下去。 SQL Server 數據庫引擎 死鎖監視器定期檢查陷入死鎖的任務。 如果監視器檢測到循環依賴關係,將選擇其中一個任務作爲犧牲品,然後終止其事務並提示錯誤。 這樣,其他任務就可以完成其事務。 對於事務以錯誤終止的應用程序,它還可以重試該事務,但通常要等到與它一起陷入死鎖的其他事務完成後執行。

sql鎖測試

摘自參考資料

1)排它鎖
新建兩個連接
在第一個連接中執行以下語句
begin tran
update table1
set A='aa'
where B='b2'
waitfor delay '00:00:30' --等待30秒
commit tran
在第二個連接中執行以下語句
begin tran
select * from table1
where B='b2'
commit tran

若同時執行上述兩個語句,則select查詢必須等待update執行完畢才能執行即要等待302)共享鎖
在第一個連接中執行以下語句
begin tran
select * from table1 holdlock -holdlock人爲加鎖
where B='b2'
waitfor delay '00:00:30' --等待30秒
commit tran

在第二個連接中執行以下語句
begin tran
select A,C from table1
where B='b2'
update table1
set A='aa'
where B='b2'
commit tran

若同時執行上述兩個語句,則第二個連接中的select查詢可以執行
而update必須等待第一個事務釋放共享鎖轉爲排它鎖後才能執行 即要等待303)死鎖
增設table2(D,E)
D E
d1 e1
d2 e2
在第一個連接中執行以下語句
begin tran
update table1
set A='aa'
where B='b2'
waitfor delay '00:00:30'
update table2
set D='d5'
where E='e1'
commit tran

在第二個連接中執行以下語句
begin tran
update table2
set D='d5'
where E='e1'
waitfor delay '00:00:10'
update table1
set A='aa'
where B='b2'
commit tran

同時執行,系統會檢測出死鎖,並中止進程

參考資料:

  • https://docs.microsoft.com/zh-cn/sql/2014-toc/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-2014
  • https://www.cnblogs.com/itcomputer/articles/5133254.html
  • https://www.cnblogs.com/yundan/p/4159940.html
  • https://blog.csdn.net/jjkang_/article/details/54925661
  • https://www.cnblogs.com/lucky-girl/p/9817117.html
  • https://blog.csdn.net/qq_34337272/article/details/81072874
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章