在現在這個雲計算,大數據,移動互聯網大行其道的時代,各類NoSQL數據庫MongoDb、redis、HBase等使用的愈來愈普遍,大有替代關係型數據庫的趨勢。可是關係型數據庫真的已經落伍了嗎?答案是否認的。非關係型數據庫不支持ACID屬性,不支持事務,沒法適應複雜查詢的缺點。關係型數據庫憑藉其強一致性的特色,註定了在相似銀行轉帳,訂單支付等場景中,仍是惟一的選擇。衆所周知,SQLSERVER經過鎖來提供ACID屬性,處理併發訪問,本文嘗試經過對鎖機制的一些學習,讓咱們明白數據庫查詢超時,死鎖等問題是如何產生的,及相關解決方法。 redis
在開始談有關鎖的內容前,咱們先看幾個咱們在平常開發中常常碰到的場景: sql
以上兩種狀況或許是由於鎖在其中"做怪"。下面咱們就開始從一些鎖的基本概念談起,瞭解SQLSERVER的鎖機制,死鎖成因以及死鎖的預防。爲了讓你們能對鎖有一個更深刻的瞭解,本文前半章談的比較多的是一些基本概念。老鳥能夠自動略過。 數據庫
一.鎖的一些基本概念 網絡
爲何須要鎖?架構
在任何多用戶的數據庫中,必須有一套用於數據修改的一致的規則,當兩個不一樣的進程試圖同時修改同一份數據時,數據庫管理系統(DBMS)負責解決它們之間潛在的衝突。任何關係數據庫必須支持事務的ACID屬性,因此在開始瞭解鎖以前,首先簡單瞭解一下數據庫事務和事務的ACID屬性。 併發
理論上全部的事務之間應該是徹底隔離的。可是實際上,要實現徹底隔離的成本實在是過高(必須是序列化的隔離等級才能徹底隔離)。因此, SQL Server經過鎖,就像十字路口的紅綠燈那樣,告訴全部併發的鏈接,在同一時刻上,那些資源能夠讀取,那些資源能夠修改。當一個事務須要訪問的資源加了其所不兼容的鎖,SQL Server會阻塞當前的事務來達成所謂的隔離性。直到其所請求資源上的鎖被釋放。 性能
爲此,SQL Server在隔離和併發之間選擇了Read Commited做爲數據庫的默認隔離級別。 學習
多個用戶同時對數據庫的併發操做時會帶來如下數據不一致的問題: 測試
髒讀:一個事務讀取到了另一個事務沒有提交的數據。 大數據
A修改了數據,隨後B又讀出該數據,但A由於某些緣由取消了對數據的修改,數據恢復原值,此時B獲得的數據就與數據庫內的數據產生了不一致
幻讀:同一事務中,用一樣的操做讀取兩次,獲得的記錄數不相同。
A讀取數據,隨後B又插入了數據,此時A再讀數據是發現先後兩次獲取的數據行集不一致
不可重複讀:在同一事務中,兩次讀取同一數據,獲得內容不一樣。
A用戶讀取數據,隨後B用戶讀出該數據並修改,此時A用戶再讀取數據時發現先後兩次的值不一致
丟失更新:事務T1讀取了數據,並執行了一些操做,而後更新數據。事務T2也作相同的事,則T1和T2更新數據時可能會覆蓋對方的更新,從而引發錯誤。
A,B兩個用戶讀同一數據並進行修改,其中一個用戶的修改結果破壞了另外一個修改的結果,好比訂票系統
併發控制的主要方法是經過鎖,在一段時間內禁止用戶作某些操做以免產生數據不一致
理解SQL SERVER中的隔離級別
爲了不上述幾種事務之間的影響,SQL Server經過設置不一樣的隔離等級來進行不一樣程度的避免。 SQL Server提供了5種選項來避免不一樣級別的事務之間的影響。隔離等級由低到高分別爲:
鎖的模式
鍵範圍鎖可防止幻讀。經過保護行之間的鍵範圍,它還能夠防止對事務訪問的記錄集進行幻插入。
鍵範圍鎖放置在索引上,指定開始鍵值和結束鍵值。此鎖將阻止任何要插入、更新或刪除任何帶有該範圍內的鍵值的行的嘗試,由於這些操做會首 先獲取索引上的鎖。例如,可序列化事務可能發出了一個 SELECT 語句,以讀取其鍵值介於 'AAA' 與 'CZZ' 之間的全部行。從 'AAA' 到 'CZZ' 範圍內的鍵值上的鍵範圍鎖可阻止其餘事務插入帶有該範圍內的鍵值(例如 'ADG'、'BBD' 或 'CAL')的行。
鎖兼容性
鎖的粒度
所謂所粒度,從本質上說就是,爲了給事務提供徹底的隔離和序列化,做爲查詢或更新的一部分被鎖定的數據的總量(的大小)。Lock Manager須要在資源的併發訪問與維護大量低級別鎖的管理開銷之間取得平衡。好比,鎖的粒度越小,可以同時訪問同一張表的併發用戶的數量就越大,不過維護這些鎖的管理開銷也越大。鎖的粒度越大,管理鎖須要的開銷就越少,而併發性也下降了。下圖說明了鎖的大小與併發性之間的權衡取捨。
SQL Server支持的鎖粒度能夠分爲爲行、頁、鍵、鍵範圍、索引、表或數據庫獲取鎖
鎖升級
鎖升級是將許多較細粒度的鎖轉換成數量更少的較粗粒度的鎖的過程,這樣能夠減小系統開銷,但卻增長了併發爭用的可能性。
當 SQL Server 數據庫引擎獲取低級別的鎖時,它還將在包含更低級別對象的對象上放置意向鎖:
除了對象上的意向鎖之外,如下對象上還須要意向頁鎖:非彙集索引的葉級頁、彙集索引的數據頁、堆數據頁。
鎖升級的閾值:
TIPS:數據庫引擎不會將行鎖或鍵範圍鎖升級到頁鎖,而是將它們直接升級到表鎖。一樣,頁鎖始終升級到表鎖。
如何查看鎖
一、使用sys.dm_tran_locks這個DMV
二、使用Profiler來捕捉鎖信息
2、死鎖成因分析
什麼是死鎖
死鎖的本質是一種僵持狀態,是多個主體對於資源的爭用而致使的。在兩個或多個任務中,若是每一個任務鎖定了其餘任務試圖鎖定的資源,此時會形成這些任務永久阻塞,從而出現死鎖。理解死鎖首先須要對死鎖所涉及的相關觀念有一個理解。
在上圖的例子中,每隊汽車都佔有一條道路,但都須要另一隊汽車所佔有的另外一條道路,所以互相阻塞,誰都沒法前行,所以形成了死鎖。
死鎖產生的緣由及四個必要條件
產生死鎖的緣由主要是:
(1) 由於系統資源不足。
(2) 進程運行推動的順序不合適。
(3) 資源分配不當等。
若是系統資源充足,進程的資源請求都可以獲得知足,死鎖出現的可能性就很低,不然就會因爭奪有限的資源而陷入死鎖。其次,進程運行推動順序與速度不一樣,也可能產生死鎖。
產生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已得到的資源保持不放。
(3) 不剝奪條件:進程已得到的資源,在末使用完以前,不能強行剝奪。
(4) 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不知足,就不會發生死鎖。
死鎖的兩種類型
一、循環死鎖:兩個進程請求不一樣資源上的鎖,每個進程都須要對方持有的該資源上的鎖,這時將發生循環死鎖。以下圖
二、轉換死鎖:兩個或多個進程都在事務中持有同一資源上的共享鎖,而且都想把它升級爲獨佔鎖,可是,誰也無法升級直到其餘的進程釋放共享鎖,以下圖
SQL Server中產生死鎖的一些狀況
一、由書籤查找產生的死鎖:這類死鎖產生的緣由是書籤查找和更新數據產生的僵持狀態。簡單來講,就是因爲Update語句對基本表產生X鎖,而後須要對錶上的索引也進行更新,而表上的索引正好被另外一個鏈接進行查找,加了S鎖,此時又產生書籤查找去基本表加了X鎖的數據進行書籤查找,此時造成死鎖
書籤查找:當查詢優化器使用非彙集索引進行查找時,若是所選擇的列或查詢條件中的列只部分包含在使用的非彙集索引和彙集索引中時,就須要一個查找(lookup)來檢索其餘字段來知足請求。對一個有聚簇索引的表來講是一個鍵查找(key lookup),對一個堆表來講是一個RID查找(RID lookup),這種查找便是——書籤查找(bookmark lookup)。簡單的說就是當你使用的sql查詢條件和select返回的列沒有徹底包含在索引列中時就會發生書籤查找。
解決方案:這種死鎖能夠經過Include列來減小書籤查找,從而減小這種類型死鎖發生的機率。
二、由外鍵產生的死鎖: 這類死鎖產生的緣由來自外鍵約束。當主表(也就是主鍵是從表外鍵的那個表)更新數據時,須要查看從表,以肯定從表的外鍵列知足外鍵約束。此時會在主表上加X鎖,但這並不能阻止同一時間,另外一個SPID向從表添加被修改的主表主鍵,爲了解決這個問題,SQL Server在進行這類更新時,使用Range鎖,這種鎖是當隔離等級爲序列化時纔有的,所以在這時雖然隔離等級多是默認的已提交讀,可是行爲倒是序列化。這極可能就會致使死鎖。
解決方案:向外鍵列添加索引,使得Range鎖加在索引上,而不是表自己。從而下降了死鎖發生的機率。
三、因爲推動順序不當產生的死鎖:在多個事務對資源的使用順序不當,造成死鎖環路而引起的。
解決方案:儘可能使資源的使用順序一致。這也是死鎖問題出現最多的一種狀況。
如何查看死鎖
3、死鎖的預防與優化
預防死鎖
預防死鎖就是破壞四個必要條件中的某一個和幾個,使其不能造成死鎖。有以下幾種辦法:
1)破壞互斥條件
破壞互斥條件有比較嚴格的限制,在SQL Server中,若是業務邏輯上容許髒讀,則能夠經過將隔離等級改成未提交讀或使用索引提示。這樣使得讀取不用加S鎖,從而避免了和其它查詢所加的與S鎖不兼容的鎖互斥,進而減小了死鎖出現的機率。
2)破壞請求和等待條件
這點因爲事務存在原子性,是不可破壞的,由於解決辦法是儘可能的減小事務的長度,事務內執行的越快越好。這也能夠減小死鎖出現的機率。
3)破壞不剝奪條件
因爲事務的原子性和一致性,不剝奪條件一樣不可破壞。但咱們能夠經過增長資源和減小資源佔用兩個角度來考慮。
增長資源:好比說經過創建非彙集索引,使得有了額外的資源,查詢不少時候就再也不索要鎖基本表,轉而鎖非彙集索引,若是索引可以"覆蓋(Cover)"查詢,那更好不過。所以索引Include列不只僅減小書籤查找來提升性能,還能減小死鎖。
減小資源佔用:好比說查詢時,能用select col1,col2這種方式,就不要用select * .這有可能帶來沒必要要的書籤查找
最大限度減小死鎖的方法
優化死鎖的一些建議
(1)對於查詢頻繁的表儘可能使用匯集索引;
(2)設法避免一次性影響大量記錄的SQL語句,特別是INSERT和UPDATE語句;
(3)設法讓UPDATE和DELETE語句使用合適的索引;
(4)使用嵌套事務時,避免提交和回退衝突;
(5)對數據一致性要求不高的查詢使用 WITH(NOLOCK)
(6)減少事務的體積,事務應最晚開啓,最先關閉,全部不是必須使用事務的操做必須放在事務外。
(7)查詢只返回你須要的列,不建議使用 SELECT * FROM 這種寫法。