這篇文章帶給大家的內容是關於MySql的事務隔離等級的詳細介紹(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。
一、事務的四大特性(ACID)
了解事務隔離等級之前不得不了解的事務的四大特性。
1、原子性(Atomicity)
交易開始後所有操作,要嘛全部做完,要嘛全部不做。事務是一個不可分割的整體。事務在執行過程中出錯,會回滾到事務開始之前的狀態,以確保事務的完整性。類似原子在物理上的解釋:指化學反應不可再分的基本微粒,原子在化學反應中不可分割 。
2、一致性(Consistency)
交易在開始和結束後,能保證資料庫完整性限制的正確性即資料的完整性。例如經典的轉帳案例,A向B轉賬,我們必須保證A扣了錢,B一定能收到錢。個人理解類似物理上的能量守恆。
3、隔離性(Isolation)
交易之間的完全隔離。例如A向一張銀行卡轉賬,避免在同一時間過多的操作導致帳戶金額的缺損,所以在A轉入結束之前是不允許其他針對此卡的操作的。
4、持久性(Durability)
交易的對資料的影響是永久性的。通俗的解釋為交易完成後,資料的操作都要進行落盤(持久化)。一旦交易完成就是不可逆的,在資料庫的操作上表現為交易一旦完成就是無法回滾的。
二、事務並發問題
在網路的大潮中,程式存在的價值早已不是在傳統產業中為了幫人們解決一些複雜的業務邏輯。使用者體驗至上的網路時代,代碼就像西二旗地鐵站碼農的腳步一樣,速度、速度、還是速度。當然也不能坐錯了方向,本來想去西直門最後到了東直門(暫且理解為正確性吧)。相對於傳統產業複雜的商業邏輯,網路更注重並髮帶給程式的速度與熱情。當然超速也是有代價的。在並發事務中,一不小心可憐的碼農就要跑路了。
1、髒讀
又稱無效資料讀出。一個事務讀取另外一個事務還沒提交的資料叫髒讀。
例如:事務T1修改了一行數據,但是還沒有提交,這時候事務T2讀取了被事務T1修改後的數據,之後事務T1因為某種原因Rollback了,那麼事務T2讀取的就是髒數據。
2、不可重複讀取
同一個交易中,多次讀出的相同資料是不一致的。
例如:事務T1讀取某一數據,事務T2讀取並修改了該數據,T1為了對讀取值進行檢驗而再次讀取該數據,便得到了不同的結果。
3、幻讀
不好表述直接上例子吧:
在倉庫管理中,管理員要給剛到的一批商品進入庫管理,當然入庫之前一定是要查一下之前有沒有入庫記錄,確保正確性。管理員A確保庫中不存在該商品之後給該商品進行入庫操作,假如這時管理員B因為手快將已將該商品進行了入庫操作。這時管理員A發現該商品已經在庫內。就像剛剛發生了幻讀一樣,本來不存在的東西,突然之間他就有了。
註:三種問題看似不太理解,髒讀著重的是資料的正確性。不可重複度側重的於資料的修改,幻讀著重於資料的新增和刪除。
三、MySql四種事務隔離等級
上一章了解了高並發下對事務的影響。事務的四種隔離等級就是以上三種問題的解決方案。
隔離等級 | 髒讀 | 無法重複度 | 幻讀 |
讀取未提交(read-uncommitted) | 是 | 是 | 是 |
#無法重複讀取(read-committed ) | 否 | 是 | |
#可重複讀取(repeatable-read) | |||
##是 | 可串列化(serializable) | 否 |
四、sql示範四種隔離等級
mysql版本:5.6
儲存引擎:InnoDB
工具:navicat
建表語句:
CREATE TABLE `tb_bank` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(16) COLLATE utf8_bin DEFAULT NULL, `account` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (1, '小明', 1000);
1、透過sql示範------read-uncommitted的髒讀
(2)read-uncommit導致的髒讀
所謂髒讀就是說,兩個事務,其中一個事務能讀取到另一個事務未提交的資料。
場景:session1要轉出200元,session2轉入100元。基數為1000。順利完成正確的結果應該是900元。但我們假設session2轉入因為某些原因事務回滾。這時正確的結果應該是800元。
示範步驟:
① 新建兩個session(會話,在navicat中表現為兩個查詢窗口,在mysql命令列中也是兩個窗口),分別執行
select @@tx_isolation;//查询当前事务隔离级别 set session transaction isolation level read uncommitted;//将事务隔离级别设置为 读未提交
② 兩個session都開啟事務
start transaction;//开启事务
③ session1和session2:證明兩個作業執行前帳戶餘額為1000
select * from tb_bank where id=1;//查询结果为1000
④ session2:此時假設session2的更新先執行。
update tb_bank set account = account + 100 where id=1;
⑤ session1:在session2 commit之前session1開始執行。
select * from tb_bank where id=1;//查询结果:1100
⑥ session2:因為某些原因,轉入失敗,事務回滾。
rollback;//事务回滚 commit;//提交事务
⑦ 這時session1開始轉出,session1覺得⑤中查詢結果1100就是正確的資料。
update tb_bank set account=1100-200 where id=1; commit;
⑧ session1 和 session2查詢結果
select * from tb_bank where id=1;//查询结果:900
這時我們發現因為session1的髒讀造成了最終資料不一致。正確的結果應該為800;
到此我們怎麼避免髒讀呢,將事務的隔離性增加一個級別到read-commit
(2)read-commit解決髒讀
重置數據,使資料恢復到account=1000
① 新建兩個session,分別設定
set session transaction isolation level read committed;//将隔离级别设置为 不可重复读
重複執行(1)中的②③④步驟
⑤ session1執行查詢
select * from tb_bank where id=1;//查询结果为1000,这说明 不可重复读 隔离级别有效的隔离了两个会话的事务。
這時我們發現,將事務的隔離升級為read-committed;後有效的隔離了兩個事務,使得session1中的事務無法查詢到session2中事務對資料的改動。有效的避免了髒讀。
2、透過sql演示-----read-committed的不可重複讀取
(1)read-commit的不可重複讀取
重置數據,使資料恢復到account=1000
所謂的不可重複讀就是說,一個事務不能讀取到另一個未提交的事務的數據,但是可以讀取到提交後的數據。這時候就造成了兩次讀取的結果不一致了。所以說是不可重複讀。
READ COMMITTED 隔離等級下,每次讀取都會重新產生一個快照,所以每次快照都是最新的,也因此事務中每次SELECT也可以看到其它已commit事務所作的更改
場景:session1進行帳戶的查詢,session2進行帳戶的轉入100。
session1開啟交易準備對帳戶進行查詢然後更新,這時session2也對帳戶開啟了交易進行更新。正確的結果應該是在session1開啟事務以後查詢讀到的結果應該是一樣的。
① 新建兩個session,分別設定
set session transaction isolation level read committed;
② session1和session2分別開啟交易
start transaction;
③ session1第一次查詢:
select * from tb_bank where id=1;//查询结果:1000
④ session2進行更新:
update tb_bank set account = account+100 where id=1; select * from tb_bank where id=1;//查询结果:1100
⑤ session1第二次查詢:
select * from tb_bank where id=1;//查询结果:1100。和③中查询结果对比,session1两次查询结果不一致。
查看查詢結果可知,session1在開啟事務期間發生重複讀取結果不一致,所以可以看到read commit事務隔離級別是不可重複讀的。顯然這種結果不是我們想要的。
重置數據,使資料恢復到account=1000
① 新建兩個session,分別設定
set session transaction isolation level repeatable read;
重複(1)中的②③④
⑤ session1第二次查詢:
select * from tb_bank where id=1;//查询结果为:1000
從結果可知,repeatable-read的隔離等級下,多次讀取結果是不受其他事務影響的。是可重複讀的。到這裡產生了一個疑問,那session1在讀到的結果中依然是session2更新前的結果,那session1中繼續轉入100能得到正確的1200的結果嗎?
繼續操作:
⑥ session1轉入100:
update tb_bank set account=account+100 where id=1;
到這裡感覺被騙了,鎖,鎖,鎖。 session1的更新語句被阻塞了。只有在session2中的update語句commit之後,session1中才能繼續執行。 session的執行結果是1200,這時發現session1並不是用1000 100計算的,因為可重複讀取的隔離等級下使用了MVCC機制,select操作不會更新版本號,是快照讀取(歷史版本)。 insert、update和delete會更新版本號,是目前讀取(目前版本)。
3、透過sql示範-----repeatable-read的幻讀
####在业务逻辑中,通常我们先获取数据库中的数据,然后在业务中判断该条件是否符合自己的业务逻辑,如果是的话,那么就可以插入一部分数据。但是mysql的快照读可能在这个过程中会产生意想不到的结果。
场景模拟:
session1开启事务,先查询有没有小张的账户信息,没有的话就插入一条。这是session2也执行和session1同样的操作。
准备工作:插入两条数据
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (2, '小红', 800); INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (3, '小磊', 6000);
(1)repeatable-read的幻读
① 新建两个session都执行
set session transaction isolation level repeatable read; start transaction; select * from tb_bank;//查询结果:(这一步很重要,直接决定了快照生成的时间)
结果都是:
② session2插入数据
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小张', 8000); select * from tb_bank;
结果数据插入成功。此时session2提交事务
commit;
③ session1进行插入
插入之前我们先看一下当前session1是否有id=4的数据
select * from tb_bank;
结果session1中没有该条记录,这时按照我们通常的业务逻辑,此时应该是能成功插入id=4的数据。继续执行:
INSERT INTO `demo`.`tb_bank`(`id`, `name`, `account`) VALUES (4, '小张', 8000);
结果插入失败,提示该条已经存在,但是我们查询里面并没有这一条数据啊。为什么会插入失败呢?
因为①中的select语句生成了快照,之后的读操作(未加读锁)都是进行的快照读,即在当前事务结束前,所有的读操作的结果都是第一次快照读产生的快照版本。疑问又来了,为什么②步骤中的select语句读到的不是快照版本呢?因为update语句会更新当前事务的快照版本。具体参阅第五章节。
重复(1)中的①②
③ session1进行插入
插入之前我们先看一下当前session1是否有id=4的数据
select * from tb_bank;
结果session1中没有该条记录,这时按照我们通常的业务逻辑,此时应该是能成功插入id=4的数据。
select * from tb_bank lock in share mode;//采用当前读
结果:发现当前结果中已经有小张的账户信息了,按照业务逻辑,我们就不在继续执行插入操作了。
这时我们发现用当前读避免了repeatable-read隔离级别下的幻读现象。
4、serializable隔离级别
在此级别下我们就不再做serializable的避免幻读的sql演示了,毕竟是给整张表都加锁的。
五、当前读和快照读
本想把当前读和快照读单开一片博客,但是为了把幻读总结明白,暂且在本章节先简单解释下快照读和当前读。后期再追加一篇MVCC,next-key的博客吧。。。
1、快照读:即一致非锁定读。
① InnoDB存储引擎下,查询语句默认执行快照读。
② RR隔离级别下一个事务中的第一次读操作会产生数据的快照。
③ update,insert,delete操作会更新快照。
四种事务隔离级别下的快照读区别:
① read-uncommitted和read-committed级别:每次读都会产生一个新的快照,每次读取的都是最新的,因此RC级别下select结果能看到其他事务对当前数据的修改,RU级别甚至能读取到其他未提交事务的数据。也因此这两个级别下数据是不可重复读的。
② repeatable-read级别:基于MVCC的并发控制,并发性能极高。第一次读会产生读数据快照,之后在当前事务中未发生快照更新的情况下,读操作都会和第一次读结果保持一致。快照产生于事务中,不同事务中的快照是完全隔离的。
③ serializable级别:从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。Serializable隔离级别下,读写冲突,因此并发度急剧下降。(锁表,不建议使用)
如何产生当前读
① select ... lock in share mode
② select ... for update
③ update,insert,delete操作都是当前读。
读取之后,还需要保证当前记录不能被其他并发事务修改,需要对当前记录加锁。①中对读取记录加S锁 (共享锁),②③X锁 (排它锁)。
① update,insert,delete操作为什么都是当前读?
简单来说,不执行当前读,数据的完整性约束就有可能遭到破坏。尤其在高并发的环境下。
分析update语句的执行步骤:update table set ... where ...;
InnoDB引擎首先進行where的查詢,查詢到的結果集從第一條開始執行當前讀,然後執行update操作,然後當前讀第二條數據,執行update操作......所以每次執行update都伴隨著目前讀取。 delete也是一樣,畢竟要先查到該資料才能刪除。 insert有點不同,insert操作執行前需要執行唯一鍵的檢查。 【相關推薦:MySQL教學】
以上是MySql的事務隔離等級的詳細介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!