來源: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、資料庫、運維等。