數(shù)據(jù)庫事務(wù)特征、數(shù)據(jù)庫隔離級別,以及各級別數(shù)據(jù)庫加鎖情況(含實操)--read uncommitted篇

1.前言

1.1 記錄什么?

1.數(shù)據(jù)庫事務(wù)特征我只是背過,并沒有很深刻的理解。
2.數(shù)據(jù)庫事務(wù)的隔離級別只是了解,并沒有深刻理解,也沒有在實際工作中體驗使用過。
3.經(jīng)常面試被人問起數(shù)據(jù)庫加鎖情況,一頭霧水,很懵。
4.在網(wǎng)上找過很多博客,有的寫得太多沒耐心看,有的寫得摘抄的定義,泛泛而談,沒有實操更沒有講解。

1.2 關(guān)于這篇分享對以上問題的解決

1.實踐出真知,如果認真讀完,并實操,實操過后反復(fù)咀嚼,相信上面的問題,除了你有沒有耐心看等主觀因素,其他的都能一一解決。
2.希望這是理解數(shù)據(jù)庫事務(wù)問題的一篇好文章。
3.如果有什么問題,請評論下, 我們多交流謝謝。

2.事務(wù)本質(zhì)剖析

2.1 什么是事務(wù)?

2.2.1 如下表格所示:

事務(wù)類別(不考慮分布式事物) 事務(wù)本質(zhì) 并發(fā)事務(wù)解決方案 并發(fā)事務(wù)方案解決的問題 并發(fā)事務(wù)解決方案實現(xiàn)原理
數(shù)據(jù)庫事務(wù)(狹義理解) 數(shù)據(jù)庫sql執(zhí)行過程 控制事務(wù)隔離級別 確保數(shù)據(jù)完整、安全、一致性,在此基礎(chǔ)上實現(xiàn)高性能訪問(魚和熊掌不可兼得) 不同的加鎖策略
應(yīng)用層事務(wù)(廣義理解) 業(yè)務(wù)邏輯 制定多線程訪問策略,如悲觀鎖(同步)、樂觀鎖(無鎖,CAS思想) 確保線程之間操作不會相互影響,保證各訪問能保證得到期望結(jié)果,并在此基礎(chǔ)上實現(xiàn)最大可能性的高性能訪問 不同的加鎖策略

對上述表格內(nèi)容的解釋
msyql事務(wù)

1.mysql:傳統(tǒng)理解 mysql 中的一次操作過程(sql 執(zhí)行)是一次事務(wù)。
2.mysql:那么多個線程 同時操作 mysql 中的數(shù)據(jù)(同一條數(shù)據(jù),一個范圍內(nèi)數(shù)據(jù))就叫并發(fā)事務(wù)。
3.mysql:數(shù)據(jù)庫層面使用不同的事務(wù)隔離級別來進行并發(fā)事務(wù)的控制,
不同的隔離級別是因為數(shù)據(jù)庫中內(nèi)部鎖機制的使用方式不同,
例如有的是在select完成之后立馬釋放鎖,有的是在整個事務(wù)commit 之后釋放鎖。

應(yīng)用層事務(wù)

1.應(yīng)用:其實每一個線程調(diào)用服務(wù)本質(zhì)上也是事務(wù)。
2.應(yīng)用:多個線程同時調(diào)用服務(wù),叫并發(fā)調(diào)用服務(wù),也可以叫并發(fā)事務(wù)。
3.應(yīng)用:應(yīng)用層應(yīng)對并發(fā)事務(wù)(訪問)解決方案有同步(悲觀鎖)、樂觀鎖(無鎖CAS)。我們對并發(fā)訪問做系統(tǒng)應(yīng)用層控制也會使用到鎖。

個人理解這就是事務(wù)的本質(zhì)。事務(wù)不應(yīng)該只僅限于數(shù)據(jù)庫。

2.2 關(guān)于ACID

舉例子說明

1.A 原子性:事務(wù)可以簡單理解為一次數(shù)據(jù)庫操作,也就是執(zhí)行sql的過程,要么執(zhí)行,要么不執(zhí)行,整個執(zhí)行結(jié)果只有兩種執(zhí)行成功,執(zhí)行失敗。
2.C 一致性:A有100塊錢,轉(zhuǎn)1塊錢給另外一個帳戶,還有99塊錢,在整個事務(wù)執(zhí)行過程中,錢數(shù)總是100塊,不會變,這就是一致性。
3.I 隔離性:事務(wù)執(zhí)行過程相互隔離,不會相互之間產(chǎn)生影響(這只是美好的愿望)。意思是多個事務(wù)并發(fā)執(zhí)行的話,結(jié)果應(yīng)該與多個事務(wù)串行執(zhí)行效果是一樣的。但并發(fā)情況下需要考慮性能,所以就需要在隔離性上做些手腳(妥協(xié)),也就是制定不同的隔離級別達到不同的并發(fā)性能。
4.D 持久性:事務(wù)每一次的執(zhí)行結(jié)果都應(yīng)該持久化(存儲)到數(shù)據(jù)庫中(磁盤數(shù)據(jù))。想想除了select,其他的update/delete/insert都會產(chǎn)生這樣的結(jié)果,持久化在應(yīng)用場景中是必須的,除非你寫了假接口。哈哈。

3.數(shù)據(jù)庫事務(wù)的隔離級別

3.1 為什么需要隔離級別?

1.四個特性之隔離性的體現(xiàn)。
2.對不同并發(fā)事務(wù)應(yīng)用場景提供不同解決方案。解決方案本質(zhì),加鎖。
3.如果不需要隔離別會出現(xiàn)什么情況?
假設(shè)一個場景,數(shù)據(jù)庫中任何數(shù)據(jù)在被并發(fā) curd 時不設(shè)置隔開級別,也就是不加鎖,情景平移,我們學(xué)習(xí)多線程時,
對線程對公共變量的并發(fā)操作不加鎖會導(dǎo)致各種異常情況的發(fā)生。
所以不設(shè)置數(shù)據(jù)庫隔離級別,我們是不能祈求數(shù)據(jù)庫中數(shù)據(jù)按照我們的預(yù)期去改變的。

現(xiàn)在我們知道數(shù)據(jù)庫 隔離級別 的必要性,接下來討論不同隔離級別會帶來的問題。

3.2 不同隔離級別帶來的問題(重要!含實操部分,最好可以實踐下)

3.2.1 前置條件--幾個概念的理解(重要)
不同隔離級別帶來的數(shù)據(jù)操作問題:

  • 1.臟讀:兩個事務(wù),t1事務(wù)可以讀取到t2事務(wù)正在做更改的數(shù)據(jù)的中間狀態(tài)(t2事務(wù)執(zhí)行過程中),而這個數(shù)據(jù)的更改有可能不會被持久化(commit),而是rollback,導(dǎo)致t1在同一事務(wù)內(nèi)的兩次讀取同一行數(shù)據(jù)得到結(jié)果不同。
  • 2.不可重復(fù)讀:t1事務(wù)在整個事務(wù)執(zhí)行過程中讀取某一條記錄多次,發(fā)現(xiàn)讀取的此條記錄不是每次都一樣。
  • 3.幻讀:t1事務(wù)在整個事務(wù)執(zhí)行過程中讀取某一范圍內(nèi)的數(shù)據(jù),在第二次讀取時發(fā)現(xiàn)多了幾行或者少了幾行。

3.2.2 數(shù)據(jù)庫中的幾種隔離級別

  • read uncommited--讀未提交
    該隔離級別指即使一個事務(wù)的更新語句沒有提交,但是別的事務(wù)可以讀到這個改變,幾種異常情況都可能出現(xiàn)。極易出錯,沒有安全性可言,基本不會使用。
  • read committed --讀已提交
    該隔離級別指一個事務(wù)只能看到其他事務(wù)的已經(jīng)提交的更新,看不到未提交的更新,消除了臟讀和第一類丟失更新,這是大多數(shù)數(shù)據(jù)庫的默認隔離級別,如Oracle,Sqlserver。
  • repeatable read --可重復(fù)讀
    該隔離級別指一個事務(wù)中進行兩次或多次同樣的對于數(shù)據(jù)內(nèi)容的查詢,得到的結(jié)果是一樣的,但不保證對于數(shù)據(jù)條數(shù)的查詢是一樣的,只要存在讀改行數(shù)據(jù)就禁止寫,消除了不可重復(fù)讀和第二類更新丟失,這是Mysql數(shù)據(jù)庫的默認隔離級別。
  • serializable --序列化讀
    意思是說這個事務(wù)執(zhí)行的時候不允許別的事務(wù)并發(fā)寫操作的執(zhí)行.完全串行化的讀,只要存在讀就禁止寫,但可以同時讀,消除了幻讀。這是事務(wù)隔離的最高級別,雖然最安全最省心,但是效率太低,一般不會用。

3.2.3 數(shù)據(jù)庫中的鎖:

  • 共享鎖(Share locks簡記為S鎖):也稱讀鎖,事務(wù)A對對象T加s鎖,其他事務(wù)也只能對T加S,多個事務(wù)可以同時讀,但不能有寫操作,直到A釋放S鎖。
  • 排它鎖(Exclusivelocks簡記為X鎖):也稱寫鎖,事務(wù)A對對象T加X鎖以后,其他事務(wù)不能對T加任何鎖,只有事務(wù)A可以讀寫對象T直到A釋放X鎖。
  • 更新鎖(簡記為U鎖):用來預(yù)定要對此對象施加X鎖,它允許其他事務(wù)讀,但不允許再施加U鎖或X鎖;當(dāng)被讀取的對象將要被更新時,則升級為X鎖,主要是用來防止死鎖的。因為使用共享鎖時,修改數(shù)據(jù)的操作分為兩步,首先獲得一個共享鎖,讀取數(shù)據(jù),然后將共享鎖升級為排它鎖,然后再執(zhí)行修改操作。這樣如果同時有兩個或多個事務(wù)同時對一個對象申請了共享鎖,在修改數(shù)據(jù)的時候,這些事務(wù)都要將共享鎖升級為排它鎖。這些事務(wù)都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個數(shù)據(jù)在修改前直接申請更新鎖,在數(shù)據(jù)修改的時候再升級為排它鎖,就可以避免死鎖。

接下來化繁為簡,配合實操,來看看每種隔離級別場景。不要覺得繁瑣,一定要讀下去。

演示場景配置:
數(shù)據(jù)庫:mysql 5.7
命令行工具:iterm2.0

1.read uncommited--讀未提交
前置條件:
1.開啟兩個 mysql 客戶端終端

開啟客戶端終端.png

2.查看當(dāng)前客戶端事務(wù)隔離級別

 命令為:select @@session.tx_isolation;
屏幕快照 2017-03-24 下午2.05.57.png

3.選擇數(shù)據(jù)庫,建立演示表test,并設(shè)置當(dāng)前客戶端事務(wù)隔離級別為read uncommitted.

1.mysql> show databases;
2.mysql> use 你的演示數(shù)據(jù)庫
3.mysql> CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
4.insert into test values(1,'張三'),(2,'李四'),(3,'王五');  
4.select @@session.tx_isolation;
5.set session transaction isolation level read uncommitted;
6.select @@session.tx_isolation;

注意:兩個客戶端都需執(zhí)行set session transaction isolation level read uncommitted;

4.客戶端1,客戶端2設(shè)置事務(wù)提交模式為 set autocommit = 0;表示關(guān)閉默認的自動提交事務(wù)功能。

命令:set autocommit = 0;

5.開啟事務(wù)

begin;

6.客戶端1 執(zhí)行 如下腳本

select name from test where id = 1 ;

結(jié)果如下圖所示:

屏幕快照 2017-03-24 下午3.53.29.png

7.客戶端2 執(zhí)行如下腳本

update test set name = '張八' where id = 1;

結(jié)果如下圖所示:


屏幕快照 2017-03-24 下午3.57.05.png

8.切換到客戶端1執(zhí)行如下腳本

select name  from test where id = 1;

結(jié)果如下圖所示:

屏幕快照 2017-03-24 下午3.58.28.png

我們發(fā)現(xiàn)此時客戶端1再次讀id = 1的記錄時,name 已經(jīng)從 ‘張三’ 更改為 ‘張八’。

我們繼續(xù)執(zhí)行下一步操作
9.客戶端2執(zhí)行回滾操作,腳本如下所示

rollback;

結(jié)果如下所示:


屏幕快照 2017-03-24 下午4.00.41.png

10.客戶端1繼續(xù)查看id = 1的記錄,如下腳本

select name from test where id = 1; 

結(jié)果如下所示:

屏幕快照 2017-03-24 下午4.02.27.png

我們發(fā)現(xiàn)在客戶端1的一次事務(wù)中id = 1 的記錄的name 發(fā)生了變化,這種變化就稱之為臟讀。
下面我們分析下 read uncommitted 情況下的加鎖情況。
吐槽一句,現(xiàn)在網(wǎng)上的博客對這個隔離級別的加鎖分析五花八門。
分為三大門派:

1.美團博客說不加鎖,鏈接在這:http://tech.meituan.com/innodb-lock.html 
2.還有說讀不加鎖(這個我認同),寫加行級共享鎖。鏈接在這:
[http://www.hollischuang.com/archives/943](http://www.hollischuang.com/archives/943)
3.還有說讀不加鎖,寫加行級排他鎖(這個我也認同,我做過實踐,稍后會演示),但是說寫完立馬釋放行級排他鎖。

那么到底是什么樣子呢,我們看一下
演示過程,打開3個命令行終端,其中兩個做演示,最后一個客戶端查詢當(dāng)前 innodb 鎖狀態(tài) 設(shè)置事務(wù)隔離級別為read uncommitted。
做如下演示:
1.客戶端1做如下操作:

update test set name = 'fxliutao' where id = 32;
屏幕快照 2017-03-26 下午2.53.34.png

2.客戶端2做與客戶端相同操作,如下所示

update test set name = 'fxliutao' where id = 32;

我們發(fā)現(xiàn)update 操作并沒有執(zhí)行,而是靜止了
如下圖所示我們分析了在客戶端2鎖等待情況下的加鎖情況:
命令為:

select * from information_schema.INNODB_LOCKS\G;
屏幕快照 2017-03-26 下午2.58.48.png

可以得出結(jié)論,read uncommitted 隔離級別下,寫操作是有鎖的,而且是 X 排他鎖,可以滅掉上述兩個門派。

并且我們看下上述客戶端2情景下的事務(wù)狀態(tài)
如下圖所示:

屏幕快照 2017-03-26 下午3.04.47.png

trx_id 為208579的代表的就是客戶端2的事務(wù),trx_state代表的是鎖狀態(tài),代表 客戶端2的事務(wù) 處于鎖等待狀態(tài),為什么是鎖等待狀態(tài)呢,因為 客戶端2的事務(wù)在更改 id = 32 的記錄時在主鍵上添加了 X(行級排他鎖) 鎖,你可能會有疑問,客戶端1 的更新動作不是已經(jīng)完成了么,那么 客戶端1 肯定已經(jīng)釋放了在主鍵 id = 32 上的排他鎖了呀,要不為什么客戶端2 能讀到客戶端1 更改 id = 32 記錄后的臟數(shù)據(jù)呢?
但是真正的真相是客戶端1在更新完后并沒有釋放排他鎖,因為如果釋放成功,那么客戶端2的事務(wù)是能將 id = 32 的記錄更新成功的,但是并沒有。那既然客戶端1在更新完后并沒有釋放排他鎖,那客戶端2為什么還能讀到臟數(shù)據(jù)呢,這跟排他鎖的屬性是相悖的呀(排他鎖會阻塞除當(dāng)前操作外的其他事務(wù)的所有讀寫操作)。
這就是最矛盾的問題,我再SqlServer的官網(wǎng)上找到這句話,事實上也正是這句話讓我茅塞頓開,如下:

Transactions running at the READ UNCOMMITTED level do not issue shared 
locks to prevent other transactions from modifying data read by the current 
transaction. READ UNCOMMITTED transactions are also not blocked by 
exclusive locks that would prevent the current transaction from reading rows 
that have been modified but not committed by other transactions. When this 
option is set, it is possible to read uncommitted modifications, which are called 
dirty reads. Values in the data can be changed and rows can appear or 
disappear in the data set before the end of the transaction. This option has the 
same effect as setting NOLOCK on all tables in all SELECT statements in a 
transaction. This is the least restrictive of the isolation levels.

對應(yīng)翻譯:

在READ UNCOMMITTED級別運行的事務(wù)不會發(fā)出共享鎖,以防止其他事務(wù)修
改當(dāng)前事務(wù)讀取的數(shù)據(jù)。讀取UNCOMMITTED事務(wù)也不被排他鎖阻止,這將阻止
當(dāng)前事務(wù)讀取已被修改但未被其他事務(wù)提交的行。設(shè)置此選項時,可以讀取未提
交的修改,稱為臟讀??梢愿臄?shù)據(jù)中的值,并且行可以在事務(wù)結(jié)束之前在數(shù)據(jù)
集中顯示或消失。此選項與在事務(wù)中的所有SELECT語句中的所有表上設(shè)置
NOLOCK具有相同的效果。這是隔離級別的最小限制。

看到了吧讀取UNCOMMITTED事務(wù)也不被排他鎖(排他鎖將阻止當(dāng)前事務(wù)讀取已被修改但未被其他事務(wù)提交的行)阻止
其實想想也對,應(yīng)為排它鎖對任何其他的事務(wù)開始之前申請的排它鎖,共享鎖都不兼容。但是如果我讀不申請鎖,就不會產(chǎn)生上述問題了呀。

所以最終結(jié)論是:read uncommitted 讀不加鎖,寫加排他鎖,并到事務(wù)結(jié)束之后釋放。

關(guān)于公眾號

精進!
道友們,你們好。早前個人就有開設(shè)公眾號的念想,今年10月終于開搞了。
我的個人的 訂閱號--T客來了;
平時自己會總結(jié)一些后端開發(fā)相關(guān)的技術(shù);
最近也迷上了音視頻開發(fā)相關(guān)技術(shù);

技術(shù)分享包括:

  • 1.FFmpeg 工程實戰(zhàn)、
  • 2.數(shù)據(jù)庫 MySQL原理與實戰(zhàn)、
  • 3.Redis中間件、
  • 4.Nginx、Java并發(fā)編程、
  • 5.Go語言方面的技術(shù)知識與實操;


    T客來了

微信掃碼就可以添加哦~

博客搬家:大坤的個人博客
歡迎評論哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容