圖解數據庫事務的隔離級別

 

前言

樂觀鎖和悲觀鎖 與 數據庫的隔離級別的關係 或者二者使用的場景是什麼?mysql

我在網上所能找到的答案,幫助我的的理解。sql

答案一:事務隔離級別是併發控制的總體解決方案,其其實是綜合利用各類類型的鎖和行版本控制,來解決併發問題。鎖是數據庫併發控制的內部機制,是基礎。對用戶來講,只有當事務隔離級別沒法解決一些併發問題和需求時,纔有必要在語句中手動設置鎖。數據庫

那麼事務隔離級別沒法解決哪些併發問題呢? 安全

先來看看事務的隔離級別: 併發

事務隔離級別

爲了解決多個事務併發會引起的問題,進行併發控制。數據庫系統提供了四種事務隔離級別供用戶選擇。編輯器

  • Read Uncommitted 讀未提交:不容許第一類更新丟失。容許髒讀,不隔離事務。高併發

  • Read Committed 讀已提交:不容許髒讀,容許不可重複讀。性能

  • Repeatable Read 可重複讀:不容許不可重複讀。但可能出現幻讀。ui

  • Serializable 串行化:全部的增刪改查串行執行。atom

讀未提交

事務讀不阻塞其餘事務讀和寫,事務寫阻塞其餘事務寫但不阻塞讀。 能夠經過寫操做加「持續-X鎖」實現。

 

 

讀已提交

事務讀不會阻塞其餘事務讀和寫,事務寫會阻塞其餘事務讀和寫。 能夠經過寫操做加「持續-X」鎖讀操做加「臨時-S鎖」實現

 

 

可重複讀

事務讀會阻塞其餘事務事務寫但不阻塞讀,事務寫會阻塞其餘事務讀和寫。 能夠經過寫操做加「持續-X」鎖,讀操做加「持續-S鎖」實現。

 

 

串行化

「行級鎖」作不到,需使用「表級鎖」。

可串行化

若是一個並行調度的結果等價於某一個串行調度的結果,那麼這個並行調度是可串行化的。

 

 

區分事務隔離級別是爲了解決髒讀、不可重複讀和幻讀三個問題的。

事務隔離級別 回滾覆蓋 髒讀 不可重複讀 提交覆蓋 幻讀
讀未提交 x 可能發生 可能發生 可能發生 可能發生
讀已提交 x x 可能發生 可能發生 可能發生
可重複讀 x x x x 可能發生
串行化 x x x x x

既然事務的隔離級別能夠作到這些。還須要悲觀鎖幹什麼呢?

個人理解是:(理解有錯誤的,請你們指正)

Mysql默認使用的隔離級別是:可重複讀

MSSQL默認使用的隔離級別是:讀已提交

若是在MSSQL使用默認使用的隔離級別時讀已提交的同事也想開發過程當中想解決:不可重複讀,提交覆蓋和幻讀等問題就可使用悲觀鎖實現。

MYSQL同理。

儘管悲觀鎖可以防止丟失更新和不可重複讀這類問題,可是它很是影響併發性能,所以應該謹慎使用。

樂觀鎖不能解決髒讀的問題,所以仍須要數據庫至少啓用「讀已提交」的事務隔離級別

經常使用的解決方案

2.悲觀鎖和共享鎖、排它鎖有是什麼關係呢?

共享鎖和排它鎖是悲觀鎖的不一樣的實現,它倆都屬於悲觀鎖的範疇。即悲觀鎖由共享鎖和排它鎖來實現的。

從讀寫角度,分共享鎖(S鎖,Shared Lock)和排他鎖(X鎖,Exclusive Lock),也叫讀鎖(Read Lock)和寫鎖(Write Lock)。理解:

持有S鎖的事務只讀不可寫。若是事務A對數據D加上S鎖後,其它事務只能對D加上S鎖而不能加X鎖。 ​ 持有X鎖的事務可讀可寫。若是事務A對數據D加上X鎖後,其它事務不能再對D加鎖,直到A對D的鎖解除

注:要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。咱們可使用命令設置MySQL爲非autocommit模式:set autocommit=0;設置完autocommit後,咱們就能夠執行咱們的正常業務了。

開始事務使用begin;/begin work;/start transaction; (三者選一就能夠);

提交事務使用commit;/commit work;

兩種鎖的具體實現以下:

  • 共享鎖:悲觀鎖都是由數據庫實現的,那共享鎖在mysql中是經過什麼命令來調用呢。經過在執行語句後面加上lock in share mode 就表明對某些資源加上共享鎖了。

好比,我這裏經過mysql打開兩個查詢編輯器,在其中開啓一個事務,並不執行commit語句。city表DDL以下

  1. CREATE TABLE `city` (

    `id` bigint(20) NOT NULL AUTO_INCREMENT,

    `name` varchar(255) DEFAULT NULL,

    `state` varchar(255) DEFAULT NULL,

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;

     

img

begin;


SELECT * from city where id = "1" lock in share mode;

而後在另外一個查詢窗口中,對id爲1的數據進行更新

img

update city set name="666" where id ="1";

此時,操做界面進入了卡頓狀態,過幾秒後,也提示錯誤信息

[SQL]update city set name="666" where id ="1"; [Err] 1205 - Lock wait timeout exceeded; try restarting transaction

那麼證實,對於id=1的記錄加鎖成功了。在上一條記錄尚未commit以前,這條id=1的記錄被鎖住了,只有在上一個事務釋放掉鎖後才能進行操做,或用共享鎖才能對此數據進行操做。

若是在上面一條記錄加上commit;

begin;


SELECT * from city where id = "1" lock in share mode;


commit;

則 update city set name="666" where id ="1";能夠執行成功!

再實驗一下:

img

update city set name="666" where id ="1" lock in share mode;

[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1

加上共享鎖後,也提示錯誤信息了,經過查詢資料才知道,對於update,insert,delete語句會自動加排它鎖的緣由

因而,我又試了試SELECT * from city where id = "1" lock in share mode;

img

這下成功了。

  • 排他鎖。排它鎖與共享鎖相對應,就是指對於多個不一樣的事務,對同一個資源只能有一把鎖。

與共享鎖類型,在須要執行的語句後面加上for update就能夠了。

排他鎖的實現以下:

  • 使用場景舉例:以MySQL InnoDB爲例

商品t_items表中有一個字段status,status爲1表明商品未被下單,status爲2表明商品已經被下單(此時該商品沒法再次下單),那麼咱們對某個商品下單時必須確保該商品status爲1。假設商品的id爲1。 若是不採用鎖,那麼操做方法以下:

//1.查詢出商品信息
select status from t_items where id=1;

//2.根據商品信息生成訂單,並插入訂單表 t_orders
insert into t_orders (id,goods_id) values (null,1)

//3.修改商品status爲2
update t_items set status=2;

可是上面這種場景在高併發訪問的狀況下極可能會出現問題。例如當第一步操做中,查詢出來的商品status爲1。可是當咱們執行第三步Update操做的時候,有可能出現其餘人先一步對商品下單把t_items中的status修改成2了,可是咱們並不知道數據已經被修改了,這樣就可能形成同一個商品被下單2次,使得數據不一致。因此說這種方式是不安全的。

  • 使用悲觀鎖來解決問題

在上面的場景中,商品信息從查詢出來到修改,中間有一個處理訂單的過程,使用悲觀鎖的原理就是,當咱們在查詢出t_items信息後就把當前的數據鎖定,直到咱們修改完畢後再解鎖。那麼在這個過程當中,由於t_items被鎖定了,就不會出現有第三者來對其進行修改了。須要注意的是,要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。咱們可使用命令設置MySQL爲非autocommit模式:set autocommit=0; 設置完autocommit後,咱們就能夠執行咱們的正常業務了。具體以下:

//0.開始事務

begin;/begin work;/start transaction; (三者選一就能夠)

//1.查詢出商品信息

select status from t_items where id=1 for update;

//2.根據商品信息生成訂單

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status爲2

update t_items set status=2;

//4.提交事務

commit;/commit work;

上面的begin/commit爲事務的開始和結束,由於在前一步咱們關閉了mysql的autocommit,因此須要手動控制事務的提交。 上面的第一步咱們執行了一次查詢操做:select status from t_items where id=1 for update;與普通查詢不同的是,咱們使用了select…for update的方式,這樣就經過數據庫實現了悲觀鎖。此時在t_items表中,id爲1的那條數據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。這樣咱們能夠保證當前的數據不會被其它事務修改(其餘事務不能讀也不能修改當前的數據)。須要注意的是,在事務中,只有SELECT ... FOR UPDATELOCK IN SHARE MODE 操做同一個數據時纔會等待其它事務結束後才執行,通常SELECT ... 則不受此影響。拿上面的實例來講,當我執行select status from t_items where id=1 for update;後。我在另外的事務中若是再次執行select status from t_items where id=1 for update;則第二個事務會一直等待第一個事務的提交,此時第二個查詢處於阻塞的狀態,可是若是我是在第二個事務中執行select status from t_items where id=1;則能正常查詢出數據,不會受第一個事務的影響。

  • Row Lock與Table Lock

使用select…for update會把數據給鎖住,不過咱們須要注意一些鎖的級別,MySQL InnoDB默認Row-Level Lock,因此只有「明確」地指定主鍵或者索引,MySQL 纔會執行Row lock (只鎖住被選取的數據) ,不然MySQL 將會執行Table Lock (將整個數據表單給鎖住。舉例以下: 一、select * from t_items where id=1 for update; 這條語句明確指定主鍵(id=1),而且有此數據(id=1的數據存在),則採用row lock。只鎖定當前這條數據。 二、select * from t_items where id=3 for update; 這條語句明確指定主鍵,可是卻查無此數據,此時不會產生lock(沒有元數據,又去lock誰呢?)。 三、select * from t_items where name='手機' for update; 這條語句沒有指定數據的主鍵,那麼此時產生table lock,即在當前事務提交前整張數據表的全部字段將沒法被查詢。 四、select * from t_items where id>0 for update; 或者select * from t_items where id<>1 for update;(注:<>在SQL中表示不等於) 上述兩條語句的主鍵都不明確,也會產生table lock。 五、select * from t_items where status=1 for update;(假設爲status字段添加了索引) 這條語句明確指定了索引,而且有此數據,則產生row lock。 六、select * from t_items where status=3 for update;(假設爲status字段添加了索引) 這條語句明確指定索引,可是根據索引查無此數據,也就不會產生lock。

參考的連接有:https://www.jianshu.com/p/71a79d838443

http://www.javashuo.com/article/p-guxaysjl-dv.html

http://www.javashuo.com/article/p-riaxoywc-gp.html

https://blog.csdn.net/yinni11/article/details/81238541

感謝這四位博主作的貢獻!

相關文章
相關標籤/搜索