本文會根據實際工作中碰到的例子,梳理清楚資料庫事務的隔離級別。內容很簡單,如果你能靜下心來看完,一定會對你理解隔離級別有很大的幫助(本文基於mysql innodb儲存引擎)。
想象乙個場景。**,如果使用者中獎了,一般有如下幾個流程:
扣減獎品數量;
記錄使用者中獎資訊;
試想如果扣減獎品數量了,結果記錄使用者中獎資料的時候失敗了,那麼資料就會出現不一致的問題。這種場景,就可以使用事務。因為事務的乙個特性,就是原子性:要麼不做,要麼全做。
上述問題解決了。再想一下這樣的場景:在**前,先查詢獎品剩餘數量,如果剩餘數量<1,則任務**活動已經結束,不再進行**。如果事務a扣減獎品數量但未提交,事務b查詢剩餘獎品數量,此時應該是多少呢?這就和事務的隔離級別有關係了。
在討論隔離級別前,我們先做一些資料庫的初始化操作:
建表:
create table `tran_test` (
`id` bigint(20) not null,
`userid` bigint(20) not null default '0',
`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;
事務a
事務bstart transaction;
start transaction;
select * from tran_test where id=1; (count=1)
update tran_test set count=count-1 where id=1;
select * from tran_test where id=1;(count=0)
select * from tran_test;(count=0)
roll back;
commit;
可以看到,事務b讀取到了事務a未提交的資料,它任務**活動已經結束。但如果此時事務a回滾,count仍然為1,則活動實際是未結束的,這就是髒讀。因此,實際中,一般不會採用這種隔離級別。
提交讀隔離級別可以解決上述髒讀問題,其只能讀到其他事務已經提交的資料。
更改資料庫隔離級別:
set global transaction isolation level read committed;
事務a
事務bstart transaction;
start transaction;
select * from tran_test where id=1; (count=1)
update tran_test set count=count-1 where id=1;
select * from tran_test;(count=0)
select * from tran_test where id=1;(count=1)
commit;
select * from tran_test where id=1;(count=0)
commit
可以看到,在事務a提交前的改動,事務b是讀取不到的。只有a事務提交後,b才能讀取到事務a的改動。
我們看到,在事務b中,先後兩次讀取,count的值是不一樣的,這就是不可重複讀。而可重複讀隔離級別可以解決這個問題。
更改資料庫隔離級別:
set global transaction isolation level repeatable read;
事務a
事務bstart transaction;
start transaction;
select * from tran_test where id=1; (count=1)
update tran_test set count=count-1 where id=1;
select * from tran_test;(count=0)
select * from tran_test where id=1;(count=1)
commit;
select * from tran_test where id=1;(count=1)
commit
可以看到,不論事務a是否提交,事務b讀到的count值都是不變的。這就是可重複讀。
除了上面提到的髒讀、不可重複讀,還有一種情況是幻讀:在事務中,前後兩次查詢,記錄數量是不一樣的。
比如事務b是事務a插入一條記錄的前後執行查詢,會發現相同的查詢條件,查出來的記錄數不一樣。由於mysql的rr(可重複讀)一併解決了幻讀的問題,所以我們直接看上述場景,在mysql中的表現:
事務a事務b
start transaction;
start transaction;
select count(1) from tran_test;(1)
insert into tran_test (id, count) value (2,2);
commit;
select count(1) from tran_test;(1)
commit
可見,在事務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才能獲得排它鎖,從而進行更新獎品的操作。
mysql隔離級別 MySQL 事務隔離級別
mysql innodb所提供的事務滿足acid的要求,事務是通過事務日誌中的redo log和undo log來實現原子性 undo log 一致性 undo log 永續性 redo log 事務通過鎖機制實現隔離性。1 事務隔離級別與實現read uncommitted 讀未提交 read c...
MySQL事務隔離級別
sql標準定義了4類隔離級別,包括了一些具體規則,用來限定事務內外的哪些改變是可見的,哪些是不可見的。低階別的隔離級一般支援更高的 併發處理,並擁有更低的系統開銷。read uncommitted 讀取未提交內容 在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,...
Mysql 事務隔離級別
mysql 5.5預設儲存引擎 表型別 使用的是innodb,它是支援acid特性的 acid,指資料庫的原子性 atomicity 一致性 consistency 隔離性 isolation 永續性 durability 乙個支援事務 transaction 的資料庫系統,必需要具有這四種特性,否...