歡迎光臨
每天分享高質量文章

MYSQL 事務隔離級別

來源:sdlyjzh ,

blog.csdn.net/sdlyjzh/article/details/79920266

本文會根據實際工作中碰到的例子,梳理清楚資料庫事務的隔離級別。內容很簡單,如果你能靜下心來看完,一定會對你理解隔離級別有很大的幫助。

想象一個場景。抽獎,如果使用者中獎了,一般有如下幾個流程:

  • 扣減獎品數量;

  • 記錄使用者中獎資訊;

  • 試想如果扣減獎品數量了,結果記錄使用者中獎資料的時候失敗了,那麼資料就會出現不一致的問題。

這種場景,就可以使用事務。因為事務的一個特性,就是原子性:要麼不做,要麼全做。

上述問題解決了。再想一下這樣的場景:

在抽獎前,先查詢獎品剩餘數量,如果剩餘數量<1,則任務抽獎活動已經結束,不再進行抽獎。如果事務A扣減獎品數量但未提交,事務B查詢剩餘獎品數量,此時應該是多少呢?這就和事務的隔離級別有關係了。

在討論隔離級別前,我們先做一些資料庫的初始化操作:

建表:

CREATE TABLE `Tran_test` (

  `id` bigint(20) NOT NULL,

  `userId` bigint(20) NOT NULL DEFAULT ‘0’,

  `weChatId` varchar(50) NOT NULL DEFAULT ” COMMENT ‘微信id(openId、uninId)’,

  `orderId` bigint(20) NOT NULL DEFAULT ‘0’ COMMENT ‘商城訂單id’,

  `count` bigint(10) DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8

初始化1個獎品:

insert into Tran_test (id,count) values(1,1)

未提交讀

事務中的修改,即使沒有提交,也會被其他事務讀取。

下麵透過mysql演示:

設定隔離級別為為提交讀:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

可以看到,事務B讀取到了事務A未提交的資料,它任務抽獎活動已經結束。但如果此時事務A回滾,count仍然為1,則活動實際是未結束的,這就是臟讀。因此,實際中,一般不會採用這種隔離級別。

提交讀

提交讀隔離級別可以解決上述臟讀問題,其只能讀到其他事務已經提交的資料。

更改資料庫隔離級別:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

可以看到,在事務A提交前的改動,事務B是讀取不到的。只有A事務提交後,B才能讀取到事務A的改動。

我們看到,在事務B中,先後兩次讀取,count的值是不一樣的,這就是不可重覆讀。而可重覆讀隔離級別可以解決這個問題。

可重覆讀

更改資料庫隔離級別:

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

可以看到,不論事務A是否提交,事務B讀到的count值都是不變的。這就是可重覆讀。

除了上面提到的臟讀、不可重覆讀,還有一種情況是幻讀:在事務中,前後兩次查詢,記錄數量是不一樣的。

比如事務B是事務A插入一條記錄的前後執行查詢,會發現相同的查詢條件,查出來的記錄數不一樣。由於mysql的RR(可重覆讀)一併解決了幻讀的問題,所以我們直接看上述場景,在mysql中的表現:

可見,在事務A提交前後,事務B查詢的結果數量是一直的,並沒有出現幻讀的情況。

一點思考

下麵預設都是討論的msyql RR隔離級別的情況。

如果兩個使用者同時抽獎,而且同時中獎。兩者都進入了中獎的事務。A事務扣減了獎品數量,B也執行了扣減數量。假設獎品數量是N,如果是可重覆讀,那麼,如果兩個事務並行進行,那麼不論A有沒有提交,B讀到的數量都是N,執行後為N-1,而事務A也是N-1,這樣不就有問題了嗎?我們期望的是N-2。

當初這個問題讓我很困惑。這反應了當時我對資料庫鎖和快照讀、當前讀兩個知識點的欠缺。

快照讀、當前讀

將設事務A已經提交,由於是可重覆讀,那事務B讀到的獎品數量一致是N,執行-1,資料變成N-1,而不是我們期望的N-2。

如果理解了快照讀和當前讀的概念,上面的困惑就不會存在了。

在事務中,執行普通select查詢之後,會建立快照,後面再執行相同的select陳述句時,查詢的其實是前面生成的快照。這也就是為什麼會有可重覆讀。

而如果執行

select * from table where ? lock in share mode;

select * from table where ? for update;

insert into table values (…); 

update table set ? where ?; 

delete from table where ?;

會執行當前讀,獲取最新資料。回到前面的問題,如果事務B執行N-1操作,會觸發當前讀,讀取事務A提交後的資料,也就是N-1,在此基礎上執行-1操作,最終N變成N-2。

併發更新

上面解決了事務A已經提交的額情況。但如果事務A更新獎品數量後但還未提交呢?此時事務B執行當前讀拿到的也是N啊。瞭解資料庫鎖機制的話,就不會有這種困惑了。事務A提交前,會一直持有排他鎖(具體是行鎖還是表鎖,要看查詢條件有沒有走索引),此時事務B更新是會阻塞的。也就是說,只有事務A提交,或回滾之後,事務B才能獲得排它鎖,從而進行更新獎品的操作。

關於資料庫的鎖,大家可以參考這篇文章:http://hedengcheng.com/?p=771


●編號377,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類公眾微信

涵蓋:程式人生、演演算法與資料結構、駭客技術與網路安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

贊(0)

分享創造快樂