了解資料庫中的事務及其并發過程中的各種限制對于合理的解決資料問題據有着重要意義,否則極有可能出現非常難以排查的由資料導緻的程式bug。
最近有同僚寫了段代碼,負責建立訂單的邏輯,代碼審查時發現可能會有并發的問題。同僚并不認同,他認為他的邏輯是寫在存儲過程中的,應該沒有問題。
代碼的邏輯大概是(僞代碼):
下面針對這個邏輯進行分析,為什麼這個事務會出現并發問題。
首先,提出兩個問題,然後帶着問題讨論事務相關的知識點,最後來解決這兩個問題并回答前文的問題。
第一個問題,事務是否可以并發?
第二個問題,資料庫是怎麼隔離事務的?
資料庫中執行事務涉及到很多方面,包括如何處理臨界資源,如何加鎖解鎖等等。但是無論事務如何執行,都需要保證以下幾個特性:
原子性
一緻性
隔離性
持久性
原子性:所有的操作是一個邏輯單元,要麼都送出成功,要麼就都失敗;
一緻性:隻有合法的資料被寫入資料庫,否則事務復原到最初的狀态;
隔離性:允許多個事務同時進行,而不會破壞資料的正确性和完整性;
持久性:事務結束後,已經送出的結果被固化儲存。
共享鎖
共享鎖用于非獨占的業務,允許多個事務同時讀取鎖定的資源,但是不允許資源被更新。
加鎖時機:執行<code>select</code>語句時預設會被加上
解鎖時機:執行完讀取後預設解除
與其他鎖相容性:資料上被設定了共享鎖,則不會允許再增加共享鎖和獨占鎖
并發性能:具有良好的并發性能
排他鎖
排他鎖,也叫獨占鎖。顧名思義,被排他鎖鎖定的資源不會允許其他事務進行任何操作。
加鎖時機:執行<code>insert,update,delete</code>時預設會被加上
解鎖時機:事務結束才能解除
相容性:如果資料上有其他鎖,不能增加獨占鎖;同樣獨占鎖存在時也不會允許增加其他鎖
并發性能:其他事務必須等待前一個事務結束後才能執行,不能并發,隻能串行
更新鎖
在更新的初始階段用于鎖定所需要的資源,防止在讀取階段使用共享鎖造成死鎖。
加鎖時機:執行<code>update</code>時,使用更新鎖鎖定相關資源
解鎖時機:讀取完畢,執行更新操作時,更新鎖更新為獨占鎖
相容性:更新鎖與共享鎖相容,即可以同時存在更新鎖和共享鎖,但隻能有一個更新鎖
并發性能:更新初期的讀取階段可以允許其他事務讀取資源,允許有限的并發;後期對資源進行獨占時不允許并發。
通用的事務隔離級别有四種,SQL Server還有另外擴充出來的級别,在此不多介紹。
Serializable(串行化)
工作方式類似于可重複讀。但它不僅會鎖定受影響的資料,還會鎖定這個範圍。這就阻止了新資料插入查詢所涉及的範圍,這種情況可以導緻幻像讀。
Repeatable Read(可重複讀)
像已送出讀級别那樣讀資料,但會保持共享鎖直到事務結束。
Read Commit
隻讀取送出的資料并等待其他事務釋放排他鎖。讀資料的共享鎖在讀操作完成後立即釋放。已送出讀是SQL Server的預設隔離級别。
Read Uncommited
在讀資料時不會檢查或使用任何鎖。是以,在這種隔離級别中可能讀取到沒有送出的資料。
答案是肯定的,資料庫中為了提高性能,允許同時進行多個事務操作,這個事務跟發起方式無關,使用存儲過程發起,或者使用代碼發起,又或者使用普通的SQL語句發起并沒有什麼差別。
要回答這個問題,先要了解資料庫中的鎖機制和資料庫事務隔離級别。資料庫中的鎖可以分為三種類型:共享鎖、獨占鎖和更新鎖。使用不同級别的鎖并配合不同的鎖定範圍已達到不同的事務隔離級别并在此基礎上并發或串行執行事務。
第三個問題,為什麼本文開頭的事務會存在并發問題?
因為事務的開始執行的是<code>select</code>,select使用的是共享鎖,有可能并發的事務在同一時間執行<code>select</code>導緻同時認為自己都是合法操作,而排隊執行後續的事務。結果導緻了實際上就有可能插入重複的資料,比如隻剩下一個商品,卻建立了兩個銷售訂單。
在事務中
根據前文所講,使用<code>insert,update或delete</code>可以在預設事務級别人為造成事務串行化,是以可以在事務内部一開始都使用<code>update</code>更新一條公共的資料,這樣的話同類型的事務都會串行化,然後再增加一個判斷語句,用于判斷後續的事務内容是否應該執行。這樣足以確定所有的操作都按照合理合法,唯一的缺點是可能造成性能問題。
在事務外
現在分布式的系統越來越多,但是再分布的系統也會有些共享資源,比如redis或zookeeper,可以利用redis或者zookeeper造一些分布式的鎖(此類屬于其他博文内容,在此不再展開)。利用事務外部的鎖将同類型的事務做一些串行化處理,再配合事務内部的檢查機制,足以確定解決事務的并發問題。
事務并發的問題及處理
資料庫事務的四大特性以及事務的隔離級别
資料庫事務和并發
SQLServer事務的隔離級别