MYSQL事務隔離級別

2021-08-18 16:42:18 字數 4158 閱讀 2703

本文會根據實際工作中碰到的例子,梳理清楚資料庫事務的隔離級別。內容很簡單,如果你能靜下心來看完,一定會對你理解隔離級別有很大的幫助(本文基於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 的資料庫系統,必需要具有這四種特性,否...