1. 程式人生 > >SQL Server與MySQL在“存在則更新,不存在則插入”併發處理上的一些差異。

SQL Server與MySQL在“存在則更新,不存在則插入”併發處理上的一些差異。

“存在則更新,不存在則插入的邏輯”併發情況下的處理

在sqlserver中:

在sqlserver中,是通過可序列化隔離級別+排它鎖的方式來鎖定一個範圍來實現的
當前鎖定一個不存在的記錄的時候,sqlserver是通過範圍鎖來實現的,具體鎖定的範圍,表中已存在的資料和當前具體判斷的Id有關
參考之前寫的一篇文章:http://www.cnblogs.com/wy123/p/7501261.html

簡單舉個例子,如下表中的表中沒有任何資料行,Id 欄位是primary key

當前Session鎖定一個不存在的記錄
在另外一個Session中試圖鎖定相同的記錄的時候被阻塞(go提交之後沒有任何返回結果,實際上是Session被阻塞)

第一個Session開水器事務

 實際上當前Session鎖定的範圍是從表中的最小值(沒有最小值就是無窮小)到無窮大的一個範圍
 也就是說說不但鎖定了當前Session鎖定的Id = 66的資料,甚至只66到正無窮大的資料也被鎖定.

以上也即就是sqlserver中範圍鎖的效果以及適應的場景,可能有其他中寫的變種,比如with(serializable),或者with(holdlock),或者先更新再判斷受影響行數啥的
本質上都是:序列化隔離級別+事務+排它鎖,不但可以鎖定已存在的記錄行,也可以鎖定不存在的記錄行。
因此不必糾結各種寫法的差異,本質都是一樣的。

set transaction
isolation level serializable; begin tran if exists (select * from TestLockNotExistId with(xlock) where Id = 66) begin --更新 update TestLockNotExistId set CreateDate = getdate() end else begin --插入 insert TestLockNotExistId values (66,'xxx
',getdate()) end commit

在MySQL中:

在MySQL中,是通過insert into values on duplicate key update語法實現的,
雖然MySQL中有類似於SQLServer中顯式加鎖的語法,也即select from where for update,原本以為可以使用 for update語法來照搬SQL Server的方式實現類似資源隔離
但是經過測試時候,mysql的for update方式顯然是鎖不住不存在的記錄的
但是select from where for update只能鎖定已存在的記錄,而鎖不住不存在的記錄

以下測試,無法鎖住不存在的記錄

可以鎖定已存在的記錄

 因此MySQL中的GAP鎖,雖然表面含義也是區間鎖(範圍鎖),與SQLServer中的範圍鎖,在細節上還是有一定的差異的。
 MySQL在預設的Reapted Read隔離級別下,雖然通過GAP鎖解決了幻讀的問題,
 但是這種鎖僅僅是在讀寫之間阻塞(互斥)的,在讀與讀之間,即便是select顯式加排它鎖的方式,不同Session的同一個不存在Id的查詢,也是不阻塞。
 因此無法通過先判斷是否存在,再決定是插入或者更新的方式來實現。