1.前言
1.1 記錄什么?
1.數據庫事務特征我只是背過,并沒有很深刻的理解。
2.數據庫事務的隔離級別只是了解,并沒有深刻理解,也沒有在實際工作中體驗使用過。
3.經常面試被人問起數據庫加鎖情況,一頭霧水,很懵。
4.在網上找過很多博客,有的寫得太多沒耐心看,有的寫得摘抄的定義,泛泛而談,沒有實操更沒有講解。
1.2 關于這篇分享對以上問題的解決
1.實踐出真知,如果認真讀完,并實操,實操過后反復咀嚼,相信上面的問題,除了你有沒有耐心看等主觀因素,其他的都能一一解決。
2.希望這是理解數據庫事務問題的一篇好文章。
3.如果有什么問題,請評論下, 我們多交流謝謝。
2.事務本質剖析
2.1 什么是事務?
2.2.1 如下表格所示:
事務類別(不考慮分布式事物) | 事務本質 | 并發事務解決方案 | 并發事務方案解決的問題 | 并發事務解決方案實現原理 |
---|---|---|---|---|
數據庫事務(狹義理解) | 數據庫sql執行過程 | 控制事務隔離級別 | 確保數據完整、安全、一致性,在此基礎上實現高性能訪問(魚和熊掌不可兼得) | 不同的加鎖策略 |
應用層事務(廣義理解) | 業務邏輯 | 制定多線程訪問策略,如悲觀鎖(同步)、樂觀鎖(無鎖,CAS思想) | 確保線程之間操作不會相互影響,保證各訪問能保證得到期望結果,并在此基礎上實現最大可能性的高性能訪問 | 不同的加鎖策略 |
對上述表格內容的解釋
msyql事務
1.mysql:傳統理解 mysql 中的一次操作過程(sql 執行)是一次事務。
2.mysql:那么多個線程 同時操作 mysql 中的數據(同一條數據,一個范圍內數據)就叫并發事務。
3.mysql:數據庫層面使用不同的事務隔離級別來進行并發事務的控制,
不同的隔離級別是因為數據庫中內部鎖機制的使用方式不同,
例如有的是在select完成之后立馬釋放鎖,有的是在整個事務commit 之后釋放鎖。
應用層事務
1.應用:其實每一個線程調用服務本質上也是事務。
2.應用:多個線程同時調用服務,叫并發調用服務,也可以叫并發事務。
3.應用:應用層應對并發事務(訪問)解決方案有同步(悲觀鎖)、樂觀鎖(無鎖CAS)。我們對并發訪問做系統應用層控制也會使用到鎖。
個人理解這就是事務的本質。事務不應該只僅限于數據庫。
2.2 關于ACID
舉例子說明
1.A 原子性:事務可以簡單理解為一次數據庫操作,也就是執行sql的過程,要么執行,要么不執行,整個執行結果只有兩種執行成功,執行失敗。
2.C 一致性:A有100塊錢,轉1塊錢給另外一個帳戶,還有99塊錢,在整個事務執行過程中,錢數總是100塊,不會變,這就是一致性。
3.I 隔離性:事務執行過程相互隔離,不會相互之間產生影響(這只是美好的愿望)。意思是多個事務并發執行的話,結果應該與多個事務串行執行效果是一樣的。但并發情況下需要考慮性能,所以就需要在隔離性上做些手腳(妥協),也就是制定不同的隔離級別達到不同的并發性能。
4.D 持久性:事務每一次的執行結果都應該持久化(存儲)到數據庫中(磁盤數據)。想想除了select,其他的update/delete/insert都會產生這樣的結果,持久化在應用場景中是必須的,除非你寫了假接口。哈哈。
3.數據庫事務的隔離級別
3.1 為什么需要隔離級別?
1.四個特性之隔離性的體現。
2.對不同并發事務應用場景提供不同解決方案。解決方案本質,加鎖。
3.如果不需要隔離別會出現什么情況?
假設一個場景,數據庫中任何數據在被并發 curd 時不設置隔開級別,也就是不加鎖,情景平移,我們學習多線程時,
對線程對公共變量的并發操作不加鎖會導致各種異常情況的發生。
所以不設置數據庫隔離級別,我們是不能祈求數據庫中數據按照我們的預期去改變的。
現在我們知道數據庫 隔離級別 的必要性,接下來討論不同隔離級別會帶來的問題。
3.2 不同隔離級別帶來的問題(重要!含實操部分,最好可以實踐下)
3.2.1 前置條件--幾個概念的理解(重要)
不同隔離級別帶來的數據操作問題:
- 1.臟讀:兩個事務,t1事務可以讀取到t2事務正在做更改的數據的中間狀態(t2事務執行過程中),而這個數據的更改有可能不會被持久化(commit),而是rollback,導致t1在同一事務內的兩次讀取同一行數據得到結果不同。
- 2.不可重復讀:t1事務在整個事務執行過程中讀取某一條記錄多次,發現讀取的此條記錄不是每次都一樣。
- 3.幻讀:t1事務在整個事務執行過程中讀取某一范圍內的數據,在第二次讀取時發現多了幾行或者少了幾行。
3.2.2 數據庫中的幾種隔離級別
- read uncommited--讀未提交
該隔離級別指即使一個事務的更新語句沒有提交,但是別的事務可以讀到這個改變,幾種異常情況都可能出現。極易出錯,沒有安全性可言,基本不會使用。- read committed --讀已提交
該隔離級別指一個事務只能看到其他事務的已經提交的更新,看不到未提交的更新,消除了臟讀和第一類丟失更新,這是大多數數據庫的默認隔離級別,如Oracle,Sqlserver。- repeatable read --可重復讀
該隔離級別指一個事務中進行兩次或多次同樣的對于數據內容的查詢,得到的結果是一樣的,但不保證對于數據條數的查詢是一樣的,只要存在讀改行數據就禁止寫,消除了不可重復讀和第二類更新丟失,這是Mysql數據庫的默認隔離級別。- serializable --序列化讀
意思是說這個事務執行的時候不允許別的事務并發寫操作的執行.完全串行化的讀,只要存在讀就禁止寫,但可以同時讀,消除了幻讀。這是事務隔離的最高級別,雖然最安全最省心,但是效率太低,一般不會用。
3.2.3 數據庫中的鎖:
- 共享鎖(Share locks簡記為S鎖):也稱讀鎖,事務A對對象T加s鎖,其他事務也只能對T加S,多個事務可以同時讀,但不能有寫操作,直到A釋放S鎖。
- 排它鎖(Exclusivelocks簡記為X鎖):也稱寫鎖,事務A對對象T加X鎖以后,其他事務不能對T加任何鎖,只有事務A可以讀寫對象T直到A釋放X鎖。
- 更新鎖(簡記為U鎖):用來預定要對此對象施加X鎖,它允許其他事務讀,但不允許再施加U鎖或X鎖;當被讀取的對象將要被更新時,則升級為X鎖,主要是用來防止死鎖的。因為使用共享鎖時,修改數據的操作分為兩步,首先獲得一個共享鎖,讀取數據,然后將共享鎖升級為排它鎖,然后再執行修改操作。這樣如果同時有兩個或多個事務同時對一個對象申請了共享鎖,在修改數據的時候,這些事務都要將共享鎖升級為排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就造成了死鎖。如果一個數據在修改前直接申請更新鎖,在數據修改的時候再升級為排它鎖,就可以避免死鎖。
接下來化繁為簡,配合實操,來看看每種隔離級別場景。不要覺得繁瑣,一定要讀下去。
演示場景配置:
數據庫:mysql 5.7
命令行工具:iterm2.0
1.read uncommited--讀未提交
前置條件:
1.開啟兩個 mysql 客戶端終端
2.查看當前客戶端事務隔離級別
命令為:select @@session.tx_isolation;
3.選擇數據庫,建立演示表test,并設置當前客戶端事務隔離級別為read uncommitted.
1.mysql> show databases;
2.mysql> use 你的演示數據庫
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;
注意:兩個客戶端都需執行set session transaction isolation level read uncommitted;
4.客戶端1,客戶端2設置事務提交模式為 set autocommit = 0;表示關閉默認的自動提交事務功能。
命令:set autocommit = 0;
5.開啟事務
begin;
6.客戶端1 執行 如下腳本
select name from test where id = 1 ;
結果如下圖所示:
7.客戶端2 執行如下腳本
update test set name = '張八' where id = 1;
結果如下圖所示:
8.切換到客戶端1執行如下腳本
select name from test where id = 1;
結果如下圖所示:
我們發現此時客戶端1再次讀id = 1的記錄時,name 已經從 ‘張三’ 更改為 ‘張八’。
我們繼續執行下一步操作
9.客戶端2執行回滾操作,腳本如下所示
rollback;
結果如下所示:
10.客戶端1繼續查看id = 1的記錄,如下腳本
select name from test where id = 1;
結果如下所示:
我們發現在客戶端1的一次事務中id = 1 的記錄的name 發生了變化,這種變化就稱之為臟讀。
下面我們分析下 read uncommitted 情況下的加鎖情況。
吐槽一句,現在網上的博客對這個隔離級別的加鎖分析五花八門。
分為三大門派:
1.美團博客說不加鎖,鏈接在這:http://tech.meituan.com/innodb-lock.html
2.還有說讀不加鎖(這個我認同),寫加行級共享鎖。鏈接在這:
[http://www.hollischuang.com/archives/943](http://www.hollischuang.com/archives/943)
3.還有說讀不加鎖,寫加行級排他鎖(這個我也認同,我做過實踐,稍后會演示),但是說寫完立馬釋放行級排他鎖。
那么到底是什么樣子呢,我們看一下:
演示過程,打開3個命令行終端,其中兩個做演示,最后一個客戶端查詢當前 innodb 鎖狀態 設置事務隔離級別為read uncommitted。
做如下演示:
1.客戶端1做如下操作:
update test set name = 'fxliutao' where id = 32;
2.客戶端2做與客戶端相同操作,如下所示
update test set name = 'fxliutao' where id = 32;
我們發現update 操作并沒有執行,而是靜止了
如下圖所示我們分析了在客戶端2鎖等待情況下的加鎖情況:
命令為:
select * from information_schema.INNODB_LOCKS\G;
可以得出結論,read uncommitted 隔離級別下,寫操作是有鎖的,而且是 X 排他鎖,可以滅掉上述兩個門派。
并且我們看下上述客戶端2情景下的事務狀態
如下圖所示:
trx_id 為208579的代表的就是客戶端2的事務,trx_state代表的是鎖狀態,代表 客戶端2的事務 處于鎖等待狀態,為什么是鎖等待狀態呢,因為 客戶端2的事務在更改 id = 32 的記錄時在主鍵上添加了 X(行級排他鎖) 鎖,你可能會有疑問,客戶端1 的更新動作不是已經完成了么,那么 客戶端1 肯定已經釋放了在主鍵 id = 32 上的排他鎖了呀,要不為什么客戶端2 能讀到客戶端1 更改 id = 32 記錄后的臟數據呢?
但是真正的真相是客戶端1在更新完后并沒有釋放排他鎖,因為如果釋放成功,那么客戶端2的事務是能將 id = 32 的記錄更新成功的,但是并沒有。那既然客戶端1在更新完后并沒有釋放排他鎖,那客戶端2為什么還能讀到臟數據呢,這跟排他鎖的屬性是相悖的呀(排他鎖會阻塞除當前操作外的其他事務的所有讀寫操作)。
這就是最矛盾的問題,我再SqlServer的官網上找到這句話,事實上也正是這句話讓我茅塞頓開,如下:
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.
對應翻譯:
在READ UNCOMMITTED級別運行的事務不會發出共享鎖,以防止其他事務修
改當前事務讀取的數據。讀取UNCOMMITTED事務也不被排他鎖阻止,這將阻止
當前事務讀取已被修改但未被其他事務提交的行。設置此選項時,可以讀取未提
交的修改,稱為臟讀。可以更改數據中的值,并且行可以在事務結束之前在數據
集中顯示或消失。此選項與在事務中的所有SELECT語句中的所有表上設置
NOLOCK具有相同的效果。這是隔離級別的最小限制。
看到了吧讀取UNCOMMITTED事務也不被排他鎖(排他鎖將阻止當前事務讀取已被修改但未被其他事務提交的行)阻止
其實想想也對,應為排它鎖對任何其他的事務開始之前申請的排它鎖,共享鎖都不兼容。但是如果我讀不申請鎖,就不會產生上述問題了呀。
所以最終結論是:read uncommitted 讀不加鎖,寫加排他鎖,并到事務結束之后釋放。
關于公眾號
精進!
道友們,你們好。早前個人就有開設公眾號的念想,今年10月終于開搞了。
我的個人的 訂閱號--T客來了;
平時自己會總結一些后端開發相關的技術;
最近也迷上了音視頻開發相關技術;
技術分享包括:
- 1.FFmpeg 工程實戰、
- 2.數據庫 MySQL原理與實戰、
- 3.Redis中間件、
- 4.Nginx、Java并發編程、
-
5.Go語言方面的技術知識與實操;
T客來了
微信掃碼就可以添加哦~
博客搬家:大坤的個人博客
歡迎評論哦~