MSSQL並發控制原先打算分為兩個部分寫:隔離級別及鎖,寫的過程中,發現需要提及下事務的相關內容,故加多一篇博文,共3篇。
並發控制,在於控制每一個事務的操作過程以及它們對資源的占用情況,同時要保證事務的ACID特性。這里簡單描述事務類別、ACID特性及對分布式事務的簡要說明。
1 事務類別
從提交方式:自動提交事務、手動提交事務
從開啟方式:顯式事務、隱式事務
其他:批范圍事務、分布式事務
1.1 顯式事務
通過API函數或者發布T-SQL begin transaction、commit transaction、commit work、rollback transaction、rollback work 、save transaction等明確定義事務的開始和結束。
這里簡要說明commit、
save transaction 、rollback 、 xact_abort。
1.1.1 COMMIT
commit,提交最近一次未提交事務,這里注意,commit transaction = commit work = commit tran [name]。每次commit,都需要把當前的@@trancount減去1。
1 begin tran yu1 2 select @@TRANCOUNT 3 begin tran yu2 4 insert into tbxin(name,age) select '第2層tran',100; 5 select @@TRANCOUNT 6 7 begin tran yu3 8 insert into tbxin(name,age) select '第3層tran',100; 9 select @@TRANCOUNT 10 commit tran --等同於 commit tran anyname 11 select @@TRANCOUNT 12 commit tran --等同於 commit tran anyname 13 select @@TRANCOUNT 14 commit tran --等同於 commit tran anyname 15 select @@TRANCOUNT

1.1.2 ROLLBACK
rollback,回滾事務,有2中語法:
- 第一個,回滾當前所有未結束事務
- rollback = rollback tran = rollback transaction = rollback work
- 無論嵌套了多少事務,@@trancount為多少,執行 rollback則直接回滾所有嵌套事務,設置@@trancount為0
- 常見錯誤案例:EXECUTE 后的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 1,當前計數 = 0。
- 第二個,回滾到某個保存點的位置
- rollback tran savepoint_name
- 不影響@@trancount計算,回滾到 某個 save tran savepoint_name的位置
錯誤案例:
1 CREATE PROC p_count 2 AS 3 begin transaction 4 insert into tbxin(name,age) select '第一層tran',200; 5 rollback transaction 6 GO 7 8 BEGIN TRAN 9 EXEC p_count 10 select @@TRANCOUNT 11 12 消息 266,級別 16,狀態 2,過程 p_count,第 0 行 13 EXECUTE 后的事務計數指示 BEGIN 和 COMMIT 語句的數目不匹配。上一計數 = 1,當前計數 = 0。
1.1.3 SAVE TRANSACTION
save transaction,定義在按條件取消某個事務的一部分后,該事務可以返回的一個位置。 如果將事務回滾到保存點,則根據需要必須完成其他剩余的 Transact-SQL 語句和 COMMIT TRANSACTION 語句,或者必須通過將事務回滾到起始點完全取消事務。 若要取消整個事務,使用窗體 ROLLBACK TRANSACTION transaction_name。 這將撤消事務的所有語句和過程。
- save transaction [savepoint_name] 提供 用戶 在事務內設置保存點或標記,所以 save transaction 只能在事務內部執行;
- save transaction [savepoint_nmae] 不影響 @@trancount 計數;
- 注意 save transaction [savepoint_name] 只有對應 rollback transaction [savepoint_name],並沒有對應 commit transaction [savepoint_name],強調下:對應的rollback transaction [savepoint_name] 是有帶 保存點名字的,如果沒有帶名字,則會回滾整個最外部的事務;
- save transaction 對應的 rollback transaction [savepoint_name]並不需要一一對應,可以多個save tran 對應0到多個rollback tran;
- 事務內,可以有多個 save transaction [savepoint_name],可使用 rollback transaction [savepoint_name] 回滾到任意一個保存點;
- 支持savepoint_name重復命名,但是不建議。在事務中允許有重復的保存點名稱,但指定保存點名稱的 ROLLBACK TRANSACTION savepoint_name 語句只將事務回滾到使用該名稱的最近的 SAVE TRANSACTION savepoint_name;
- 在使用 BEGIN DISTRIBUTED TRANSACTION 顯式啟動或從本地事務升級的分布式事務中,不支持 SAVE TRANSACTION。
1 begin tran 2 select @@TRANCOUNT 3 4 save tran yu1 5 insert into tbxin(name,age) select '第1層save',100; 6 select @@TRANCOUNT 7 8 save tran yu2 9 insert into tbxin(name,age) select '第2層save',100; 10 select @@TRANCOUNT 11 12 save tran yu3 13 insert into tbxin(name,age) select '第3層save',100; 14 select @@TRANCOUNT 15 16 save tran yu4 17 insert into tbxin(name,age) select '第4層save',100; 18 select @@TRANCOUNT 19 20 rollback tran yu3 21 select @@TRANCOUNT 22 23 commit tran yu2 24 select @@TRANCOUNT 25 26 rollback tran

1.1.4 XACT_ABORT
xact_abort用於設置環境屬性,默認為關閉狀態。在關閉的狀態下,嵌套事務中,若某個嵌套事務異常,不影響整個事務的進行,需手動寫明錯誤后的處理方式(commit or rollback);啟動狀態下,當某個嵌套事務異常,回滾整個嵌套事務。
1.2 隱式事務
為連接將隱性事務模式設置為打開之后,當數據庫引擎實例首次執行下列任何語句時,都會自動啟動一個事務:當連接以隱式事務模式進行操作時,數據庫引擎實例將在提交或回滾當前事務后自動啟動新事務。無須描述事務的開始,只需提交或回滾每個事務。隱性事務模式生成連續的事務鏈。通過 API 函數或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 語句,將隱性事務模式設置為打開。
- ALTER TABLE
- CREATE
- DROP
- OPEN
- FETCH
- GRANT
- REVOKE
- SELECT
- UPDATE
- DELETE
- INSERT
- TRUNCATE TABLE
1.3 自動提交事務
--例子 INSERT INTO ...
--數據庫默認的事務管理模式,在沒有被顯式事務及隱式事務覆蓋的情況下,自動在每個Tsql完成時,提交或者回滾
1.4 手動提交事務
--例子 BEGIN TRAN INSERT INTO ... COMMIT / ROLLBACK --數據庫默認的事務管理模式,顯式事務在完成時,手動指定SQL,說明提交或者回滾。
1.5 批范圍事務
只能應用於多個活動結果集 (MARS),在 MARS 會話中啟動的 Transact-SQL 顯式或隱式事務變為批處理級事務。當批處理完成時沒有提交或回滾的批處理級事務自動由 SQL Server 進行回滾。
1.6 分布式事務
分布式事務跨越兩個或多個稱為資源管理器的服務器,有同構分布式及異構分布式。在MSSQL中,可以通過
BEGIN DISTRIBUTED TRANSACTION 命令開啟分布式事務。
這里有一點需要注意一下:當事務內部操作跨越了多個服務器,但是並沒有使用 BEGIN DISTRIBUTED TRANSACTION 命令開頭的事務,也會自動轉化為分布式事務!
假設A服務器上的數據庫 Orders 用於記錄訂單,B服務器上的 Stock 用於記錄庫存(不考慮程序建立兩個DB連接,按照僅建立一個 DB鏈接到 A 上)。當 Stock有庫存,並減去一個庫存后,Orders 正常可以下一個訂單,則這個下單事務內,操作了兩個服務器,屬於分布式事務,簡要操作如下:
BEGIN DISTRIBUTED TRANSACTION SELECT ... FROM Stock... WHERE ... UPDATE Stock ... INSERT INTO ORDERS ... COMMIT
在SQL SERVER中,如果沒有配置服務器的DTC服務,使用分布式事務的時候,會報錯如下:
(1 行受影響) 消息 8501,級別 16,狀態 3,第 4 行 服務器 'XINYSU\MSSQL' 上的 MSDTC 不可用。 (1 行受影響) 鏈接服務器"test_xinysu"的 OLE DB 訪問接口 "SQLNCLI11" 返回了消息 "該伙伴事務管理器已經禁止了它對遠程/網絡事務的支持。"。 消息 7391,級別 16,狀態 2,第 4 行 無法執行該操作,因為鏈接服務器 "test_xinysu" 的 OLE DB 訪問接口 "SQLNCLI11" 無法啟動分布式事務。
如果實例需要支持分布式事務,則需要在雙方的服務器其上開啟DTC服務,運行XA事務。這里注意一點,如果鏈接服務器是MySQL數據庫,因為mysql的odbc不支持 XA事務,所以,會報錯 無法啟動分布式事務。官網解釋如下:
MySQL OLE DB driver does not support MSDTC because it doesn’t support auto-enlistment in the ambient COM+ transaction. If you really want to, you can write your own XA.DLL to wrap MySQL OLEDB driver in an XA transaction.
windows啟動DTC服務 ,配置如下:
"管理工具" -> "組件服務" ,按照下圖操作,打開本地的DTC配置,啟動網絡DTC,啟動XA事務,如下圖。
配置結束后點擊應用,則會提示MSDTC服務會被重啟,同時,依賴DSDTC的應用程序可能需要重啟才能使用新的配置,這個時候就需要重啟 SQL SERVER的服務了。
如果是正在跑的數據庫,建議先從庫配置,切換主從,主庫配置的順序來啟用。最好是在搭建服務器一開始,就了解程序層面是否會使用到這個功能。正常情況下,都是在程序中配置多個DB連接,由程序來控制分布式事務。
這里多說一個日常需要注意的事項,在使用鏈接服務器對另外一個服務器上的DB做增刪改操作的時候,都是一行一行傳遞過去做操作的。比如:
1 INSERT INTO mssql_xinysu.dbname.dbo.tbid(id) select id from sys.sysobjects 2 或者 3 INSERT INTO openquery([mysql_xinysu],'select id from tbid') select id from sys.sysobjects
假設 select id from sys.sysobjects 的結果有2k行,則在 第一個SQL的 ODBC 是這么處理的:
- 分為2000個INSERT 語句
- (@Param000004 int)INSERT [dbname].[dbo].[tbid]([id]) VALUES(@Param000004)
- 一條一條INSERT
第二個SQL的ODBC是這么處理的:
- 分為2000個INSERT 語句
- INSERT INTO tbid ([id]) VALUES(單個的ID值)
- 一條條INSERT
通過這個操作說明,可想而知,連接服務器操作是非常慢的過程,不建議在有性能要求的業務上進行這樣的使用操作。
2 ACID特性
2.1 Atomicity
簡稱為A,事務的原子性。要求 在同個事務中,單個SQL或者多個SQL對數據進行的修改操作,要么一起執行提交,要么全部回滾不提交。存儲引擎中,通過undo log 來實現事務的原子性。
例子:
2.2 Consistency
簡稱為 C,事務的一致性。事務在完成時,必須使所有的數據都保持一致狀態 。在相關數據庫中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。事務結束時,所有的內部數據結構(如 B 樹索引或雙向鏈表)都必須是正確的。
一致性,可以分為兩個層次來說明。一個是存儲引擎層,一個是業務層。
在存儲引擎層,當某個表格發現了數據修改,那么修改的行數據應該符合表格的約束條件、外鍵條件,涉及到索引頁數據也要做相應修改,保證數據修改后的完整性。
在業務層,事務的一致性更多的在於程序設計,比如在一個事務內,賬號A給賬號B轉賬100元,那么這個事務結束后,A的賬號需要少100元,B的賬號需要增加100元。
2.3 Isolation
簡稱為 I,事務的隔離性。由並發事務所做的修改必須與任何其他並發事務所做的修改隔離。在SQL SERVER中,通過設置隔離級別來保證。事務識別數據時數據所處的狀態,要么是另一並發事務修改它之前的狀態,要么是第二個事務修改它之后的狀態,事務不會識別中間狀態的數據。這稱為可串行性,因為它能夠重新裝載起始數據,並且重播一系列事務,以使數據結束時的狀態與原始事務執行的狀態相同。
根據業務的實際情況和需求,可以對不同的業務選擇不同的隔離級別 來 控制 該事務 與其他並發事務之間的 隔離關系,隔離級別越高,並發能力就相應越低。
2.4 Durability
簡稱為D,事務的持久性。完成完全持久的事務之后,它的影響將永久存在於系統中。該修改即使出現系統故障也將一直保持。存儲引擎中,通過redo log 來實現事務的持久性。
3 協議
在事務內,除了保持ACID的特性外,在MSSQL中,會遵循相應的兩階段鎖跟XA協議。
3.1 2PL
兩階段鎖2PL,這個在之前的博文
http://www.cnblogs.com/xinysu/p/7260227.html 中有所提及。
2-PL,也就是兩階段鎖,鎖的操作分為兩個階段:加鎖、解鎖。先加鎖,后解鎖,不相交。加鎖時,讀操作會申請並占用S鎖,寫操作會申請並占用X鎖,如果對所在記錄加鎖有沖突,那么會處於等待狀態,知道加鎖成功才驚醒下一步操作。解鎖時,也就是事務提交或者回滾的時候,這個階段會釋放該事務中所有的加鎖情況,進行一一釋放鎖。
假設事務對記錄A和記錄B都有操作,那么,其加鎖解鎖按照逐行加鎖解鎖順序,如下:
BEGIN LOCK A READ A A:A+100 WRITE A UNLOCK A LOCK B READ B UNLOCK B COMMIT
兩階段鎖還有幾種特殊情況:conservative(保守)、strict(嚴格)、strong strict(強嚴格),這三種類型在加鎖和釋放鎖的處理有些不一樣。
- conservative
- 在事務開始的時候,獲取需要的記錄的鎖,避免在操作期間逐個申請鎖可能造成的鎖等待,conservative 2PL 可以避免死鎖
- strict
- 僅在事務結束的時候(commit or rollback),才釋放所有 write lock,read lock 則正常釋放
- strong strict
- 僅在事務結束的時候(commit or rollback),才釋放所有鎖,包括write lock 跟 read lock 都是結束后才釋放。
3.2 XA
說XA協議,不得不提2PC、3PC協議,而提這兩個協議,又不得不說說CAP理論。
3.2.1 CAP理論
CAP分別是Consitency(一致性)、Availability(可用性)、Partition tolerance(分區容錯性),在分布式存儲系統里邊,最多只能同時滿足其中兩者。
- Consitency
- 一致性,在分布式存儲系統中,對於每一次的讀操作,對於讀到的數據,要么都是最新的,要么則返回一個錯誤
- 這里的CAP的C跟ACID的C雖然是一個單詞,但是含義不一樣哦,記得區分
- 在關系型數據庫里邊,通常優先考慮到是一致性
- Availability
- 可用性,保證每次請求都正常,但不要求返回的結果是最新的數據
- Partition tolerance
- 分區容錯性,當各個分區之間因為網絡發生消息丟失或者延遲是,分布式存儲系統仍能正常運行。
- 則是在操作 涉及多個服務器的事務 過程中,
3.2.2 2PC
兩階段提交,全程是 Two Phase Commitment Protocol。也就是將分布式事務分為了兩個階段:Prepare 跟 Commit/Rollback。
在分布式系統中, 如果其中一個服務器上的操作失敗,則其他服務器上的操作也需要回滾,即在一個事務內,多個跨服務器的操作中,要么所有都成功,要么所有都失敗。但是呢,實際上每個節點都可以對自身的操作做控制,但是卻不能控制同個分布式系統的其他節點的操作,這也就導致了,同個事務中,如果A節點操作成功,但是B節點操作失敗,如果沒有相應的處理方式,則會出現,某些操作成功,某些操作失敗,造成數據不一致的情況。
如何處理這個問題呢?這就需要引入一個第三方組件來統一接收各個節點的操作情況,然后再根據各個操作的結果來判斷各個節點的操作是否提交或者回滾。這個就是 2PC的雛形了。
2PC的簡要處理說明如下:
- Prepare
- 事務協調器coordinator 對 涉及到的節點 發起 操作申請;
- 各個節點獲取到 操作后,直接在 數據庫中執行,並存放相關的日志到redo / undo log中,注意注意,這里僅是操作,並沒有提交或者回滾該操作;
- 各節點將處理日志寫入磁盤;
- 返回信息給coordinnator
- 如果節點可以正常執行,則返回 Ready 通知 coordinator;
- 如果節點不可以正常執行,則該節點本地回滾該操作,並返回 Not Ready 通知 coordinator
- Comiit/Rollback
- coordinator 根據各個節點 的反饋信息,來決定 該事務操作的結果
- coordinator將 操作結果記錄到日志中
- 反饋操作結果給各個節點
- 如果出現一個或者一個以上的節點 反饋回來 Not Ready的通知,則coordinator會通知 正常執行操作的節點 回滾事務
- 如果沒有出現 Not Ready 的反饋,則coordinator會通知所有節點 COMMIT 操作。
2PC如何處理異常?
- 事務協調器宕機
- 這里需要引入一個新角色:coordinator watchdog,事務協調器看門狗
- 無論是coordinator 還是分布式系統的各個節點,在操作過程中,都會記錄當前操作的狀態日志。當出現異常或者恢復時,可以通過日志來判斷當前的情況。
- 當 coordinator 發起提議后宕機,而此時各個節點開始操作,然后反饋給 coordinator,但是 遲遲沒有接收到 coordinator 的回應,那么各個節點的操作就無法回滾或者提交,處於堵塞情況。而 coordinator watchdog 則可以解決這個堵塞現象,當coordinator宕機一定時間后,看門狗會自動 擔任 coordinator 的工作,接收各個節點的 反饋情況,然后再根據反饋結果傳遞 COMMIT/ROLLBAK給各個節點。
- 節點宕機
- prepare階段宕機,則coordinator接收到事務后發送給各個節點需要做的 操作時,節點發生宕機,這個時候,則該節點無法返回 Ready 的消息,coordinator則默認接受該節點發出的 abort 信息,coordinator通知其他各個節點 Rollback 操作;
- Comiit/Rollback階段宕機,由於各個節點及coordinator都有日志記錄,coordinator會記錄這個事務是會提交還是回滾,當 節點宕機后,其他節點根據coordinator的通知執行ROLLBACK或者COMMIT,而宕機節點本地會記錄該事務操作未執行提交或者回滾,節點恢復后,會從 coordinator 日志中讀取日志,重新處理該操作。
3.2.3 XA協議
XA協議是 X/Open DTP Group 定義的兩階段提交協議,規定了事務管理器跟資源管理器的接口。
事務管理器指的是 二階段協議中的 coordinator ,而資源管理器則指的是各個數據庫系統。
一般情況下,各個數據庫系統並不知道彼此之間做了什么,這個時候,就需要一個第三方來做信息的接受跟傳達,由它通知和協調相關數據庫的提交或回滾。XA就是用來定義這個第三方的協議,詳細定義了交易中間件與數據庫之間的接口規范(即接口函數),交易中間件用它來通知數據庫事務的開始、結束以及提交、回滾等。
XA接口函數由數據庫廠商提供。通常情況下,交易中間件與數據庫通過XA 接口規范,使用兩階段提交來完成一個全局事務,XA規范的基礎是兩階段提交協議。注意,XA事務的性能相對較差。
參考文檔:
