1. 程式人生 > >數據庫中的事務和並發問題探討

數據庫中的事務和並發問題探討

back zab 時間 www 同時存在 sql 執行 table keyword

數據庫中的事務和並發問題探討

引子

最近有同事寫了段代碼,負責創建訂單的邏輯,代碼審查時發現可能會有並發的問題。同事並不認同,他認為他的邏輯是寫在存儲過程中的,應該沒有問題。

代碼的邏輯大概是(偽代碼):

begin transaction

if 查詢到客戶存在進行中的訂單
     rollback transaction

if 查詢到設備存在進行中的訂單
     rollback transaction
    
插入訂單

commit transaction

下面針對這個邏輯進行分析,為什麽這個事務會出現並發問題。

事務概述

首先,提出兩個問題,然後帶著問題討論事務相關的知識點,最後來解決這兩個問題並回答前文的問題。

第一個問題,事務是否可以並發?

第二個問題,數據庫是怎麽隔離事務的?

事務的表現特性

數據庫中執行事務涉及到很多方面,包括如何處理臨界資源,如何加鎖解鎖等等。但是無論事務如何執行,都需要保證以下幾個特性:

  • 原子性
  • 一致性
  • 隔離性
  • 持久性

原子性:所有的操作是一個邏輯單元,要麽都提交成功,要麽就都失敗;

一致性:只有合法的數據被寫入數據庫,否則事務回滾到最初的狀態;

隔離性:允許多個事務同時進行,而不會破壞數據的正確性和完整性;

持久性:事務結束後,已經提交的結果被固化保存。

數據庫的各種鎖

  1. 共享鎖

共享鎖用於非獨占的業務,允許多個事務同時讀取鎖定的資源,但是不允許資源被更新。

  • 加鎖時機:執行select
    語句時默認會被加上
  • 解鎖時機:執行完讀取後默認解除
  • 與其他鎖兼容性:數據上被設置了共享鎖,則不會允許再增加共享鎖和獨占鎖
  • 並發性能:具有良好的並發性能
  1. 排他鎖

排他鎖,也叫獨占鎖。顧名思義,被排他鎖鎖定的資源不會允許其他事務進行任何操作。

  • 加鎖時機:執行insert,update,delete時默認會被加上
  • 解鎖時機:事務結束才能解除
  • 兼容性:如果數據上有其他鎖,不能增加獨占鎖;同樣獨占鎖存在時也不會允許增加其他鎖
  • 並發性能:其他事務必須等待前一個事務結束後才能執行,不能並發,只能串行
  1. 更新鎖

在更新的初始階段用於鎖定所需要的資源,防止在讀取階段使用共享鎖造成死鎖。

  • 加鎖時機:執行update
    時,使用更新鎖鎖定相關資源
  • 解鎖時機:讀取完畢,執行更新操作時,更新鎖升級為獨占鎖
  • 兼容性:更新鎖與共享鎖兼容,即可以同時存在更新鎖和共享鎖,但只能有一個更新鎖
  • 並發性能:更新初期的讀取階段可以允許其他事務讀取資源,允許有限的並發;後期對資源進行獨占時不允許並發。

事務隔離級別

通用的事務隔離級別有四種,SQL Server還有另外擴展出來的級別,在此不多介紹。

  1. Serializable(串行化)

工作方式類似於可重復讀。但它不僅會鎖定受影響的數據,還會鎖定這個範圍。這就阻止了新數據插入查詢所涉及的範圍,這種情況可以導致幻像讀。

  1. Repeatable Read(可重復讀)

像已提交讀級別那樣讀數據,但會保持共享鎖直到事務結束。

  1. Read Commit

只讀取提交的數據並等待其他事務釋放排他鎖。讀數據的共享鎖在讀操作完成後立即釋放。已提交讀是SQL Server的默認隔離級別。

  1. Read Uncommited

在讀數據時不會檢查或使用任何鎖。因此,在這種隔離級別中可能讀取到沒有提交的數據。

回答前文的問題

第一個問題,事務是否可以並發?

答案是肯定的,數據庫中為了提高性能,允許同時進行多個事務操作,這個事務跟發起方式無關,使用存儲過程發起,或者使用代碼發起,又或者使用普通的SQL語句發起並沒有什麽區別。

第二個問題,數據庫是怎麽隔離事務的?

要回答這個問題,先要理解數據庫中的鎖機制和數據庫事務隔離級別。數據庫中的鎖可以分為三種類型:共享鎖、獨占鎖和更新鎖。使用不同級別的鎖並配合不同的鎖定範圍已達到不同的事務隔離級別並在此基礎上並發或串行執行事務。

第三個問題,為什麽本文開頭的事務會存在並發問題?

因為事務的開始執行的是select,select使用的是共享鎖,有可能並發的事務在同一時間執行select導致同時認為自己都是合法操作,而排隊執行後續的事務。結果導致了實際上就有可能插入重復的數據,比如只剩下一個商品,卻創建了兩個銷售訂單。

如何防止並發問題

  1. 在事務中

根據前文所講,使用insert,update或delete可以在默認事務級別人為造成事務串行化,因此可以在事務內部一開始都使用update更新一條公共的數據,這樣的話同類型的事務都會串行化,然後再增加一個判斷語句,用於判斷後續的事務內容是否應該執行。這樣足以確保所有的操作都按照合理合法,唯一的缺點是可能造成性能問題。

  1. 在事務外

現在分布式的系統越來越多,但是再分布的系統也會有些共享資源,比如redis或zookeeper,可以利用redis或者zookeeper造一些分布式的鎖(此類屬於其他博文內容,在此不再展開)。利用事務外部的鎖將同類型的事務做一些串行化處理,再配合事務內部的檢查機制,足以確保解決事務的並發問題。

參考資料

  • 事務並發的問題及處理
  • 數據庫事務的四大特性以及事務的隔離級別
  • 數據庫事務和並發
  • SQLServer事務的隔離級別

數據庫中的事務和並發問題探討