偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

詳解 Seata AT 模式事務(wù)隔離級(jí)別與全局鎖設(shè)計(jì)

運(yùn)維 數(shù)據(jù)庫(kù)運(yùn)維
Seata AT 模式是一種非侵入式的分布式事務(wù)解決方案,Seata 在內(nèi)部做了對(duì)數(shù)據(jù)庫(kù)操作的代理層,我們使用 Seata AT 模式時(shí),實(shí)際上用的是 Seata 自帶的數(shù)據(jù)源代理 DataSourceProxy,Seata 在這層代理中加入了很多邏輯,比如插入回滾 undo_log 日志,檢查全局鎖等。

[[442164]]

Seata AT 模式是一種非侵入式的分布式事務(wù)解決方案,Seata 在內(nèi)部做了對(duì)數(shù)據(jù)庫(kù)操作的代理層,我們使用 Seata AT 模式時(shí),實(shí)際上用的是 Seata 自帶的數(shù)據(jù)源代理 DataSourceProxy,Seata 在這層代理中加入了很多邏輯,比如插入回滾 undo_log 日志,檢查全局鎖等。

為什么要檢查全局鎖呢,這是由于 Seata AT 模式的事務(wù)隔離是建立在支事務(wù)的本地隔離級(jí)別基礎(chǔ)之上的,在數(shù)據(jù)庫(kù)本地隔離級(jí)別讀已提交或以上的前提下,Seata 設(shè)計(jì)了由事務(wù)協(xié)調(diào)器維護(hù)的全局寫排他鎖,來(lái)保證事務(wù)間的寫隔離,同時(shí),將全局事務(wù)默認(rèn)定義在讀未提交的隔離級(jí)別上。

Seata 事務(wù)隔離級(jí)別解讀

在講 Seata 事務(wù)隔離級(jí)之前,我們先來(lái)回顧一下數(shù)據(jù)庫(kù)事務(wù)的隔離級(jí)別,目前數(shù)據(jù)庫(kù)事務(wù)的隔離級(jí)別一共有 4 種,由低到高分別為:

  • Read uncommitted:讀未提交
  • Read committed:讀已提交
  • Repeatable read:可重復(fù)讀
  • Serializable:序列化

數(shù)據(jù)庫(kù)一般默認(rèn)的隔離級(jí)別為讀已提交,比如 Oracle,也有一些數(shù)據(jù)的默認(rèn)隔離級(jí)別為可重復(fù)讀,比如 Mysql,一般而言,數(shù)據(jù)庫(kù)的讀已提交能夠滿足業(yè)務(wù)絕大部分場(chǎng)景了。

我們知道 Seata 的事務(wù)是一個(gè)全局事務(wù),它包含了若干個(gè)分支本地事務(wù),在全局事務(wù)執(zhí)行過(guò)程中(全局事務(wù)還沒(méi)執(zhí)行完),某個(gè)本地事務(wù)提交了,如果 Seata 沒(méi)有采取任務(wù)措施,則會(huì)導(dǎo)致已提交的本地事務(wù)被讀取,造成臟讀,如果數(shù)據(jù)在全局事務(wù)提交前已提交的本地事務(wù)被修改,則會(huì)造成臟寫。

由此可以看出,傳統(tǒng)意義的臟讀是讀到了未提交的數(shù)據(jù),Seata 臟讀是讀到了全局事務(wù)下未提交的數(shù)據(jù),全局事務(wù)可能包含多個(gè)本地事務(wù),某個(gè)本地事務(wù)提交了不代表全局事務(wù)提交了。

在絕大部分應(yīng)用在讀已提交的隔離級(jí)別下工作是沒(méi)有問(wèn)題的,而實(shí)際上,這當(dāng)中又有絕大多數(shù)的應(yīng)用場(chǎng)景,實(shí)際上工作在讀未提交的隔離級(jí)別下同樣沒(méi)有問(wèn)題。

在極端場(chǎng)景下,應(yīng)用如果需要達(dá)到全局的讀已提交,Seata 也提供了全局鎖機(jī)制實(shí)現(xiàn)全局事務(wù)讀已提交。但是默認(rèn)情況下,Seata 的全局事務(wù)是工作在讀未提交隔離級(jí)別的,保證絕大多數(shù)場(chǎng)景的高效性。

全局鎖實(shí)現(xiàn)

AT 模式下,會(huì)使用 Seata 內(nèi)部數(shù)據(jù)源代理 DataSourceProxy,全局鎖的實(shí)現(xiàn)就是隱藏在這個(gè)代理中。我們分別在執(zhí)行、提交的過(guò)程都做了什么。

1、執(zhí)行過(guò)程

執(zhí)行過(guò)程在 StatementProxy 類,在執(zhí)行過(guò)程中,如果執(zhí)行 SQL 是 select for update,則會(huì)使用 SelectForUpdateExecutor 類,如果執(zhí)行方法中帶有 @GlobalTransactional or @GlobalLock注解,則會(huì)檢查是否有全局鎖,如果當(dāng)前存在全局鎖,則會(huì)回滾本地事務(wù),通過(guò) while 循環(huán)不斷地重新競(jìng)爭(zhēng)獲取本地鎖和全局鎖。

  1. public T doExecute(Object... args) throws Throwable { 
  2.     Connection conn = statementProxy.getConnection(); 
  3.     // ... ... 
  4.     try { 
  5.         // ... ... 
  6.         while (true) { 
  7.             try { 
  8.                 // ... ... 
  9.                 if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) { 
  10.                     // Do the same thing under either @GlobalTransactional or @GlobalLock,  
  11.                     // that only check the global lock  here. 
  12.                     statementProxy.getConnectionProxy().checkLock(lockKeys); 
  13.                 } else { 
  14.                     throw new RuntimeException("Unknown situation!"); 
  15.                 } 
  16.                 break; 
  17.             } catch (LockConflictException lce) { 
  18.                 if (sp != null) { 
  19.                     conn.rollback(sp); 
  20.                 } else { 
  21.                     conn.rollback(); 
  22.                 } 
  23.                 // trigger retry 
  24.                 lockRetryController.sleep(lce); 
  25.             } 
  26.         } 
  27.     } finally { 
  28.         // ... 
  29.     } 

2、提交過(guò)程

提交過(guò)程在 ConnectionProxy#doCommit方法中。

1)如果執(zhí)行方法中帶有@GlobalTransactional注解,則會(huì)在注冊(cè)分支時(shí)候獲取全局鎖:

  • 請(qǐng)求 TC 注冊(cè)分支
  1. private void register() throws TransactionException { 
  2.     if (!context.hasUndoLog() || !context.hasLockKey()) { 
  3.         return
  4.     } 
  5.     Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), 
  6.                                                                 null, context.getXid(), null, context.buildLockKeys()); 
  7.     context.setBranchId(branchId); 
  • TC 注冊(cè)分支的時(shí)候,獲取全局鎖
  1. protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession) throws TransactionException { 
  2.     if (!branchSession.lock()) { 
  3.         throw new BranchTransactionException(LockKeyConflict, String 
  4.                                              .format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(), 
  5.                                                      branchSession.getBranchId())); 
  6.     } 

2)如果執(zhí)行方法中帶有@GlobalLock注解,在提交前會(huì)查詢?nèi)宙i是否存在,如果存在則拋異常:

io.seata.rm.datasource.ConnectionProxy#processLocalCommitWithGlobalLocks

  1. private void processLocalCommitWithGlobalLocks() throws SQLException { 
  2.     checkLock(context.buildLockKeys()); 
  3.     try { 
  4.         targetConnection.commit(); 
  5.     } catch (Throwable ex) { 
  6.         throw new SQLException(ex); 
  7.     } 
  8.     context.reset(); 

GlobalLock 注解說(shuō)明

從執(zhí)行過(guò)程和提交過(guò)程可以看出,既然開(kāi)啟全局事務(wù) @GlobalTransactional注解可以在事務(wù)提交前,查詢?nèi)宙i是否存在,那為什么 Seata 還要設(shè)計(jì)多處一個(gè) @GlobalLock注解呢?

因?yàn)椴⒉皇撬械臄?shù)據(jù)庫(kù)操作都需要開(kāi)啟全局事務(wù),而開(kāi)啟全局事務(wù)是一個(gè)比較重的操作,需要向 TC 發(fā)起開(kāi)啟全局事務(wù)等 RPC 過(guò)程,而@GlobalLock注解只會(huì)在執(zhí)行過(guò)程中查詢?nèi)宙i是否存在,不會(huì)去開(kāi)啟全局事務(wù),因此在不需要全局事務(wù),而又需要檢查全局鎖避免臟讀臟寫時(shí),使用@GlobalLock注解是一個(gè)更加輕量的操作。

如何防止臟寫

先來(lái)看一下使用 Seata AT 模式是怎么產(chǎn)生臟寫的:

注:分支事務(wù)執(zhí)行過(guò)程省略其它過(guò)程。

業(yè)務(wù)一開(kāi)啟全局事務(wù),其中包含分支事務(wù)A(修改 A)和分支事務(wù) B(修改 B),業(yè)務(wù)二修改 A,其中業(yè)務(wù)一執(zhí)行分支事務(wù) A 先獲取本地鎖,業(yè)務(wù)二則等待業(yè)務(wù)一執(zhí)行完分支事務(wù) A 之后,獲得本地鎖修改 A 并入庫(kù),業(yè)務(wù)一在執(zhí)行分支事務(wù)時(shí)發(fā)生異常了,由于分支事務(wù) A 的數(shù)據(jù)被業(yè)務(wù)二修改,導(dǎo)致業(yè)務(wù)一的全局事務(wù)無(wú)法回滾。

如何防止臟寫?

1、業(yè)務(wù)二執(zhí)行時(shí)加 @GlobalTransactional注解:

注:分支事務(wù)執(zhí)行過(guò)程省略其它過(guò)程。

業(yè)務(wù)二在執(zhí)行全局事務(wù)過(guò)程中,分支事務(wù) A 提交前注冊(cè)分支事務(wù)獲取全局鎖時(shí),發(fā)現(xiàn)業(yè)務(wù)業(yè)務(wù)一全局鎖還沒(méi)執(zhí)行完,因此業(yè)務(wù)二提交不了,拋異常回滾,所以不會(huì)發(fā)生臟寫。

2、業(yè)務(wù)二執(zhí)行時(shí)加 @GlobalLock注解:

注:分支事務(wù)執(zhí)行過(guò)程省略其它過(guò)程。

與 @GlobalTransactional注解效果類似,只不過(guò)不需要開(kāi)啟全局事務(wù),只在本地事務(wù)提交前,檢查全局鎖是否存在。

2、業(yè)務(wù)二執(zhí)行時(shí)加 @GlobalLock 注解 + select for update語(yǔ)句:

注:分支事務(wù)執(zhí)行過(guò)程省略其它過(guò)程。

如果加了select for update語(yǔ)句,則會(huì)在 update 前檢查全局鎖是否存在,只有當(dāng)全局鎖釋放之后,業(yè)務(wù)二才能開(kāi)始執(zhí)行 updateA 操作。

如果單單是 transactional,那么就有可能會(huì)出現(xiàn)臟寫,根本原因是沒(méi)有 Globallock 注解時(shí),不會(huì)檢查全局鎖,這可能會(huì)導(dǎo)致另外一個(gè)全局事務(wù)回滾時(shí),發(fā)現(xiàn)某個(gè)分支事務(wù)被臟寫了。所以加 select for update 也有個(gè)好處,就是可以重試。

如何防止臟讀

Seata AT 模式的臟讀是指在全局事務(wù)未提交前,被其它業(yè)務(wù)讀到已提交的分支事務(wù)的數(shù)據(jù),本質(zhì)上是Seata默認(rèn)的全局事務(wù)是讀未提交。

那么怎么避免臟讀現(xiàn)象呢?

業(yè)務(wù)二查詢 A 時(shí)加 @GlobalLock 注解 + select for update語(yǔ)句:

注:分支事務(wù)執(zhí)行過(guò)程省略其它過(guò)程。

加select for update語(yǔ)句會(huì)在執(zhí)行 SQL 前檢查全局鎖是否存在,只有當(dāng)全局鎖完成之后,才能繼續(xù)執(zhí)行 SQL,這樣就防止了臟讀。

本文轉(zhuǎn)載自微信公眾號(hào)「后端進(jìn)階」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系后端進(jìn)階公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 后端進(jìn)階
相關(guān)推薦

2010-11-19 16:13:06

oracle事務(wù)隔離級(jí)

2024-12-02 08:37:04

2009-06-29 17:54:47

Spring事務(wù)隔離

2018-12-19 16:46:38

MySQL事務(wù)隔離數(shù)據(jù)庫(kù)

2025-01-13 13:12:54

2020-10-13 10:32:24

MySQL事務(wù)MVCC

2021-08-04 13:19:42

MySQL 事務(wù)隔離

2021-07-26 10:28:13

MySQL事務(wù)隔離

2024-04-26 09:17:20

MySQL事務(wù)隔離

2022-07-03 14:03:57

分布式Seata

2020-09-21 18:44:35

MySQL

2020-03-05 09:33:15

數(shù)據(jù)庫(kù)事務(wù)隔離事務(wù)

2022-09-13 13:49:05

數(shù)據(jù)庫(kù)隔離

2021-01-18 11:49:26

面試事務(wù)隔離

2020-02-21 20:10:13

搞懂事務(wù)隔離級(jí)別

2025-02-08 10:56:18

2022-09-19 06:16:23

事務(wù)隔離級(jí)別Spring

2023-10-11 08:09:53

事務(wù)隔離級(jí)別

2021-10-19 10:10:51

MySQL事務(wù)隔離級(jí)別數(shù)據(jù)庫(kù)

2024-02-01 09:18:20

TCC模式Seata
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)