事務隔離級別,髒讀、不可重複讀、幻讀,樂觀鎖、悲觀鎖(共享鎖、排它鎖)

    數據庫事務具有四個特徵,分別是原子性(Atomicity)、一致性(Consistency)、隔離性(Isoation)、持久性(Durability),簡稱爲事務的ACID特性。

    事務的隔離性是指在併發環境中,併發的事務是相互隔離的。SQL標準中定義了四種數據庫事務隔離級別,級別從低到高分別爲:讀未提交(Read Uncommitted)、讀已提交(Read Committed)、可重複讀(Repeatable Read)、串行化(Serializable)。在事務的併發操作中會出現髒讀不可重複讀幻讀。在事務的併發操作中第二類更新丟失可以通過樂觀鎖悲觀鎖解決。

--------------------------------------------------------------------------------

讀未提交(Read Uncommitted)

 

  • 該隔離級別,所有事務都可以看到其他未提交事務的執行結果。通俗地講就是,在一個事務中可以讀取到另一個事務中新增或修改但未提交的數據。
  • 該隔離級別可能導致的問題是髒讀。因爲另一個事務可能回滾,所以在第一個事務中讀取到的數據很可能是無效的髒數據,造成髒讀現象。
> set tx_isolation='READ-UNCOMMITTED';
事務A 事務B 備註
> begin;
> select name,blance from account where name='jack';
jack  1000
 

事務A開啓,查詢jack餘額1000

  > begin;
> select name,blance from account where name='jack';
jack  1000
> update account set blance=1200 where name='jack';
> select name,blance from account where name='jack';
jack  1200

事務B開啓,修改餘額爲1200,

但是沒有提交事務

> select name,blance from account where name='jack';
jack  1200
 

事務A中查詢jack餘額爲1200,讀

取到了事務B未提交的修改,即

和之前讀到的結果不同,

不可重復讀

  > rollback;
> select name,blance from account where name='jack';
jack  1000
事務B回滾
> select name,blance from account where name='jack';
jack  1000
> commit;
 

事務A又讀取到了jack之前的餘額。

 

讀已提交(Read Committed)

 

  • 這是大多數數據庫系統的默認隔離級別(但不是mysql默認的)
  • 一個事務只能看見已經提交事務所做的修改。換句話說,一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。
  • 該隔離級別可能導致的問題是不可重複讀。因爲兩次執行同樣的查詢,可能會得到不一樣的結果。
> set tx_isolation='READ-COMMITTED';
事務A 事務B 備註
> begin;
> select name,blance from account where name='jack';
jack  1000
  事務A開啓,查詢jack餘額1000
  > begin;
> select name,blance from account where name='jack';
jack  1000
> update account set blance=1200 where name='jack';
> select name,blance from account where name='jack';
jack  1200

事務B開啓,修改餘額爲1200,

但是沒有提交事務

> select name,blance from account where name='jack';
jack  1000
 

事務A中查詢jack餘額仍爲1000,

沒有讀取到事務B未提交的修改

  > commit;
> select name,blance from account where name='jack';
jack  1200
事務B提交
> select name,blance from account where name='jack';
jack  1200
> commit;
 

事務A讀取到了事務B提交的修改,

和之前讀到的結果不同,不可重復讀

 

可重複讀(Repeatable Read)

 

  • 這是MySQL的默認事務隔離級別
  • 它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。通俗來講,可重複讀在一個事務裏讀取數據,怎麼讀都不會變,除非提交了該事務,再次進行讀取。
  • 該隔離級別存在的問題是幻讀
set tx_isolation='REPEATABLE-READ';
事務A 事務B 備註
> begin;
> select name,blance from account where name='jack';
jack  1000
  事務A開啓,查詢jack餘額1000
  > begin;
> select name,blance from account where name='jack';
jack  1000
> update account set blance=1200 where name='jack';
> select name,blance from account where name='jack';
jack  1200

事務B開啓,修改餘額爲1200,

但是沒有提交事務

> select name,blance from account where name='jack';
jack  1000
 

事務A中查詢jack餘額仍爲1000,

沒有讀取到了事務B未提交的修改

  > commit;
> select name,blance from account where name='jack';
jack  1200
事務B提交
> select name,blance from account where name='jack';
jack  1000
> commit;
> select name,blance from account where name='jack';
jack  1200
 

事務A中查詢jack餘額仍爲1000,

沒有讀取到事務B已提交的修改

下面看下如何出現幻讀的

事務A 事務B     備註
> begin;
> select name,blance from account;
jack  1000
  事務A開啓,查詢到jack賬戶
  > begin;
> select name,blance from account;
jack  1000
> insert into account (name,blance) values ('tom',800);
> select name,blance from account;
jack  1000
tom   800
commit;
事務B開啓,新增tom賬戶
> select name,blance from account;
jack  1000
> insert into account (name,blance) values ('tom',800);
ERROR 1062 (23000): Duplicate entry 'tom' for key 'uk_name'
rollback;
 

事務A再次查詢時,只看到jack

賬戶,新增tom賬戶時報唯一鍵衝

突。明明沒有查詢到tom賬

戶,但是有新增衝突,這就是

幻讀。如果事務B新增或刪

除數據,事務A進行更新時,

不是預期的影響行數,也是幻讀

 

串行化(Serializable)

 

  • 這是最高的隔離級別
  • 它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。通俗地講就是,假如兩個事務都操作到同一數據行,那麼這個數據行就會被鎖定,只允許先讀取/操作到數據行的事務優先操作,只有當事務提交了,數據行纔會解鎖,後一個事務才能成功操作這個數據行,否則只能一直等待
  • 該隔離級別可能導致大量的超時現象和鎖競爭。
> set tx_isolation='SERIALIZABLE';
事務A 事務B 事務C 備註
> begin;
> select * from account where ctime='2018-06-12 10:28:13';
1  jack  1000  2018-06-12 10:28:13  2018-06-12 16:48:58
    事務A開啓,並且查詢了jack賬戶,並拿到了該行的共享鎖
  > begin;
> select * from account where ctime='2018-06-12 10:28:13';
1  jack  1000  2018-06-12 10:28:13  2018-06-12 16:48:58
> update account set blance=1200 where utime='2018-06-12 16:48:58';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  事務B開啓,能夠正常查詢jack賬戶(可以拿到該行的共享鎖),但是不能修改(不可以拿到該行的排它鎖)
> commit;     事務A提交,釋放了jack賬戶的共享鎖
  > update account set blance=1200 where utime='2018-06-12 16:48:58';
> select * from account where ctime='2018-06-12 10:28:13';
1  jack  1200  2018-06-12 10:28:13  2018-06-12 16:48:58
  事務B可以修改賬戶jack了,即拿到了該行的排它鎖
    > begin;
> select * from account where ctime='2018-06-12 10:28:13';
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
事務C開啓,不能查詢jack賬戶,因爲事務B拿了改行的排它鎖,導致事務C拿不到該行的共享鎖
  > commit;   事務B提交,釋放了jack賬戶的排它鎖
    > select * from account where ctime='2018-06-12 10:28:13';
1  jack  1200  2018-06-12 10:28:13  2018-06-12 17:06:02
commit;
事務C可以查詢jack賬戶了,即拿到了該行的共享鎖

注:

  1. 這裏使用ctime、utime字段進行查詢和修改,是爲了展示使用非索引字段也有事務隔離效果
  2. 在該隔離級別中,select語句會自動獲取共享鎖,update/insert/delete會自動獲取排它鎖
  3. 一個事務中的一行記錄的共享鎖被拿走,其他事務只能獲取改行的共享鎖,不能獲取排它鎖;一個事務中的一行記錄的排它鎖被拿走,其他事務不能獲取該行的共享鎖和排它鎖

--------------------------------------------------------------------------------

從上面四種數據庫事務隔離級別介紹可以對應出解決的問題,如圖:

--------------------------------------------------------------------------------

 

第一類更新丟失

SQL標準中對此沒有定義,不會出現該錯誤。

事務A 事務B
開啓事務  
查詢jack餘額爲1000  
  開啓事務
  查詢jack餘額爲1000
  更新jack餘額爲1200
  提交事務
更新jack餘額爲800  
回滾事務  

查詢jack餘額爲1000

(事務B的更新丟失)

 

 

第二類更新丟失

事務A 事務B
開啓事務  
查詢jack餘額爲1000  
  開啓事務
  查詢jack餘額爲1000
  更新jack餘額爲1200
  提交事務
更新jack餘額爲800  
事務提交  

查詢jack餘額爲800

(事務B的更新丟失)

 

 

第二類更新丟失解決辦法

樂觀鎖

    在更新語句中增加過濾條件,進行版本的判斷,可以是這條記錄的版本號、更新時間等。然後通過影響行數來判斷是否更新成功。

> begin;
> select name,blance,utime from account where name='jack';
jack  1000  2018-06-12 18:20:06
> update account set blance=1200 where name='jack' and utime='2018-06-12 18:20:06';
> commit;

悲觀鎖

    悲觀鎖分爲共享鎖和排它鎖。

    共享鎖又稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一數據可以共享一把鎖,共享鎖是用來讀取數據的。另外,一個事務獲取了同一數據的共享鎖,其他事務就不能獲取該數據的排它鎖。

    排它鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排它鎖就是不能與其他所並存,如一個事務獲取了一個數據行的排它鎖,其他事務就不能再獲取該行的其它鎖,包括共享鎖和排它鎖。另外不存什麼事務隔離級別,update/insert/delete會自動獲取排它鎖

    共享鎖獲取方式:select * from account where name='jack' lock in share mode;

    排它鎖獲取方式:select * from account where name='jack' for update;

MySQL分爲表級鎖和行級鎖,共享鎖和排它鎖是行級鎖。表級鎖在此不做討論。

最佳實踐

    通常,對於絕大多數的應用程序來說,可以優先考慮將數據庫系統的隔離級別設置爲讀已提交(Read Committed),這能夠在避免髒讀的同時保證較好的併發性能。儘管這種事務隔離級別會導致不可重複讀、幻讀和第二類丟失更新等併發問題,但較爲科學的做法是在可能出現這類問題的個別場合中,由應用程序主動採用悲觀鎖或樂觀鎖來進行事務控制。

另本文中示例的表結構如下:

CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(16) NOT NULL DEFAULT '' COMMENT '用戶名',
  `blance` int(11) NOT NULL DEFAULT '0' COMMENT '餘額',
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '寫入時間',
  `utime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶餘額表';

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章