天天看點

TiDB 悲觀事務模式和Mysql的表象差別

前篇

tidb在3.0.8之後預設開啟悲觀事務,但是autocommit 事務優先采用樂觀事務送出,翻譯一下這句話就是,如果是不手動開啟事務的場景,同時兩個insert/update語句還是走的樂觀鎖機制,就有機率觸發 ​

​write conflict​

​ 觸發方式如圖:

先 ​

​SET @@tidb_txn_mode = '';​

​ 調整本次會話為樂觀模式

​​

TiDB 悲觀事務模式和Mysql的表象差別

​​

解決 ​

​write conflict​

有兩個辦法

1:在預設悲觀事務的情況下開啟事務(有點繞,說的再簡單點就是java要加上 ​

​@Transactional​

​ 注解)

2:開啟樂觀事務重試機制,并重建立立tidb連接配接(建立立的連接配接才會用上新參數)

SET GLOBAL tidb_disable_txn_auto_retry = OFF;
SET GLOBAL tidb_retry_limit = 10;      

第一點很好了解,問題出在于第二點,因為官方原話為

TiDB 預設不進行事務重試,因為重試事務可能會導緻更新丢失,進而破壞可重複讀的隔離級别。

當事務中存在依賴查詢結果來更新的語句時,重試将無法保證事務原本可重複讀的隔離級别,最終可能導緻結果與預期出現不一緻。

這兩句話其實很難了解,讓我們來看下圖的例子

例子1

同樣要先 ​

​SET @@tidb_txn_mode = '';​

​ 調整本次會話為樂觀模式,然後開啟重試機制

​​

TiDB 悲觀事務模式和Mysql的表象差別

​​

最後結果也就是t6的更新其實并沒有成功,因為實際這個時候的id=1資料已經被session B更新為了status=0,在重試的時候自然比對不上更新條件.然後我們再回過頭來看官方的原話,因為重試事務可能會導緻更新丢失.但是這個真的是更新丢失嗎,我們回想下在如果以上操作在mysql下的場景,在t6這一步的時候,mysql會觸發目前讀,然後直接告訴你0 row affectied(忘記截圖了,可以自己試下),那麼在t9這個時間的時候,mysql和tidb的表象其實是一緻的,差別在于中間更新的時候傳回的影響行數

例子2

讓我們再來看一個例子

| | | | | -- | ---------------------------------------------------------- | ---------------------------------------- | | | session a | session b | | t1 | ​

​begin​

​​ | | | t2 | | ​

​begin​

​​ | | t3 | ​

​update tidb set status =0 where id = 1​

​​ | | | t4 | | ​

​update tidb set status =1 where id = 1​

​​ | | t5 | | ​

​commit​

​​ | | t6 | ​

​commit​

​​ | | | t7 | ​

​SELECT * from tidb where id = 1​

​ id name status 1 tidb 0 | |

可以看出在這個例子裡,update資料的時間不重要,commit的時間才重要,後面commit的資料會把先commit的資料進行覆寫,對于mysql來說,在t4這一步就會被阻斷,直到session a送出事務,是以在這個場景下,tidb和mysql是完全不一樣的

總結下

1.可能會造成傳回的更新條數與實際情況不同,但是最終表象會和mysql一緻

2.自然時間的更新順序将沒有參考意義,資料的最新記錄與commit時間有關,這一點和mysql不一緻

幻讀

再額外說下幻讀

可以先看下這個文章看下mysql的幻讀

​​​https://www.wolai.com/jtaGKJqoUusS5mmA5NqoG1​​

由于tidb沒有間隙鎖

是以再這個場景下,tidb的表象也和mysql不一緻

| | | | | -- | ------------------------------------------- | ------------------------------------------------------------- | | | session a | session b | | t1 | ​

​begin;​

​​ | | | t2 | | ​

​begin;​

​​ | | t3 | ​

​SELECT * from tidb where id >3 for UPDATE​

​​ | | | | | | | t4 | | ​

​INSERT INTO tidb (id, name, status) VALUES (12, 'tidb', 7);​

​​ | | t5 | | ​

​commit;​

​​ | | t6 | ​

​SELECT * from tidb where id >3 for UPDATE​

​ | | | | | | | | 可以查詢到insert的資料 | |

在mysql的場景下,在t4這一步就會被阻塞,直到t3加的鎖被釋放

其他參考文章

​​TiDB和MySQL的鎖一些分析比對​​ 技術文章

【是否原創】是 【首發管道】TiDB 社群 【首發管道連結】其他平台首發請附上對應連結 【正文】 ​​[image]​​ 圖1 鎖分類圖 一、悲觀鎖和樂觀鎖 TiDB一開始是樂觀鎖,但自TiDB3.0版本開始,支援悲觀事務,并且在3.0.8版本開始預設使用悲觀事務,支援悲觀鎖,檢視事務:show variables like ‘%tidb_txn_mode%’; [image] 悲觀鎖的…