關於學習這件事情寧肯花點時間系統學習,也不要東一榔頭西一棒槌,都說學習最好的方式就是系統的學習,但願看完本文會讓你對事務有必定的理解。數據庫版本爲8.0
mysql
系列文章
1. 揭開MySQL索引神祕面紗
2. MySQL查詢優化必備
3. 上來就問MySQL事務,瑟瑟發抖...
sql
1、什麼是事務
事務是獨立的工做單元,在這個獨立工做單元中全部操做要麼所有成功,要麼所有失敗。數據庫
也就是說若是有任何一條語句由於崩潰或者其它緣由致使執行失敗,那麼未執行的語句都不會再執行,已經執行的語句會進行回滾操做,這個過程被稱之爲事務。緩存
例:安全
最近在寫一個論壇系統,當發佈的主題被其它用戶舉報後,後臺會對舉報內容進行審覈。服務器
一經審覈爲違規主題,則進行刪除主題的操做,但不只僅要刪除主題還要刪除主題下的帖子、瀏覽量,關於這個主題的一切信息都須要進行清理。併發
刪除流程以下,用上邊概念來講,如下執行的四個流程,每一個流程都必須成功不然事務回滾返回刪除失敗。mvc
假設執行到了第三步後SQL執行失敗了,那麼第一二步都會進行回滾,第四步則不會在執行。高併發
2、事務四大特徵
事務的四大特徵,原子性、一致性、隔離性、持久性。性能
1. 原子性
事務中全部操做要麼所有成功,要麼所有失敗,不會存在一部分紅功,一部分失敗。
這個概念也是事務最核心
的特性,事務概念自己就是使用原子性進行定義的。
原子性的實現是基於回滾日誌實現(undo log),當事務須要回滾時就會調用回滾日誌進行SQL語句回滾操做,實現數據還原。
2. 一致性
一致性,字面意思就是先後一致唄!在數據庫中無論進行任何操做,都是從一個一致性轉移到另外一個一致性。
當事務結束後,數據庫的完整性約束不被破壞。
當你瞭解完事務的四大特徵以後就會發現,都是保證數據一致性爲最終目標存在的。
在學習事務的過程當中你們看到最多的案例就是轉帳,假設用戶A與用戶B餘額共計1000,那麼無論怎麼轉倆人的餘額自始至終也就只有1000。
3. 隔離性
保證事務執行儘量的不受其它事務影響,這個是隔離級別能夠自行設置,在innodb中默認的隔離級別爲可重複讀(Repeatable Read)。
這種隔離級別有可能形成的問題就是出現幻讀,可是使用間隙鎖能夠解決幻讀問題。
學習了隔離性你須要知道原子性和持久性是針對單個事務
,而隔離性是針對事務與事務之間的關係。
4. 持久性
持久性是指當事務提交以後,數據的狀態就是永久的,不會由於系統崩潰而丟失。
事務持久性是基於重作日誌(redo log)實現的。
3、事務併發會出現的問題
1. 髒讀
讀取了另外一個事務沒有提交的數據。
事務A | 事務B |
---|---|
執行事務 | 執行事務 |
主題訪問量從100修改到150 | |
查詢主題訪問量爲150 | |
提交事務 |
以上表爲例,事務A讀取主題訪問量時讀取到了事務B沒有提交的數據150。
若是事務B失敗進行回滾,那麼修改後的值仍是會回到100。
然而事務A獲取的數據是修改後的數據,這就有問題了。
2. 不可重複讀
事務讀取同一個數據,返回結果前後不一致問題。
事務A | 事務B |
---|---|
執行事務 | 執行事務 |
查詢主題訪問量爲100 | |
修改主題訪問量爲200 | |
提交事務 | |
查詢主題訪問量爲200 |
上表格中,事務A在前後獲取主題訪問量時,返回的數據不一致。
也就是說在事務A執行的過程當中,訪問量被其它事務修改,那麼事務A查詢到的結果就是不可靠的。
**髒讀與不可重複讀的區別**
髒讀讀取的是另外一個事務沒有提交的數據,而不可重複讀讀取的是另外一個事務已經提交的數據。
3. 幻讀
事務按照範圍
查詢,倆次返回結果不一樣。
事務A | 事務B |
---|---|
開始事務 | 開始事務 |
查詢訪問量100-200的主題個數爲100 | |
此時有一篇新的文章訪問量達到了150 | |
提交事務 | |
再次查詢訪問量100-200的主題個數爲101 |
以上表爲例,當對100-200訪問量的主題作統計時,第一次找到了100個,第二次找到了101個。
4. 區別
- 髒讀讀取的是另外一個事務沒有提交的數據,而不可重複讀讀取的是另外一個事務已經提交的數據。
- 幻讀和不可重複讀都是讀取了另外一條已經提交的事務(這點與髒讀不一樣),所不一樣的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據總體(好比數據的個數)。
針對以上的三個問題,產生了四種隔離級別。
在第二節中對隔離性進行了簡單的概念解釋,實際上的隔離性是很複雜的。
在MySQL中定義了四種隔離級別,分別爲未提交讀 (Read Uncommitted)、提交讀 (Read committed)、可重複讀取 (Repeatable Read)、可串行化 (Serializable)。
- 未提交讀 (Read Uncommitted):倆個事務同時運行,有一個事務修改了數據,但未提交,另外一個事務是能夠讀取到沒有提交的數據。這種狀況被稱之爲
髒讀
。 - 提交讀(Read committed):一個事務在未提交以前,所作的任何操做其它事務不可見。這種隔離級別也被稱之爲
不可重複讀
。由於會存在倆次一樣的查詢,返回的數據可能會獲得不同的結果。 - 可重複讀(Repeatable Read):這種隔離級別
解決了髒讀
問題,可是仍是存在幻讀
問題,這種隔離界別在MySQL的innodb引擎中是默認級別。MySQL在解決幻讀問題使用間隙鎖來解決幻讀問題。 - 可串行化 (Serializable):這種級別是最高的,強制事務進行串行執行,解決了可重複讀的幻讀問題。
隔離級別 | 髒讀 | 不可重讀讀 | 幻讀 |
---|---|---|---|
未提交讀 (Read Uncommitted) | 可能發生 | 可能發生 | 可能發生 |
提交讀(Read committed) | 不可能發生 | 可能發生 | 可能發生 |
可重複讀(Repeatable Read) | 不可能發生 | 不可能發生 | 可能發生 |
可串行化 (Serializable) | 不可能發生 | 不可能發生 | 不可能發生 |
對於隔離級別,級別越高併發就越低,而級別越低會引起髒讀、不可重複讀、幻讀的問題。
所以在MySQL中使用可重複讀(Repeatable Read)
做爲默認級別。
做爲默認級別是如何解決並處理相應問題的呢!
那麼針對這一問題,是一個難啃的骨頭,咔咔將在下一期MVCC文章專門來介紹這塊。
4、事務日誌以及事務異常如何應對
MySQL的版本號爲8.0
在Innodb中事務的日誌分爲倆種,回滾日誌、重作日誌。
先來看一下倆個日誌的存放位置吧!
在Linux下的MySQL事務日誌存放在/var/lib/mysql
這個位置中。
從上圖中能夠看到分別爲ib_logfile、undo_倆個文件。
ib_logfile文件爲重作日誌
undo_文件爲回滾日誌
在這裏估計有點小夥伴會有點迷糊這個回滾日誌。
那是由於在MySQL5.6默認回滾日誌沒有進行獨立表空間存儲,而是存放到了ibdata文件中。
獨立表空間存儲從MySQL5.6後就已經支持了,可是須要自行配置。
在MySQL8.0是由innodb_undo_tablespaces
這個參數來設置回滾日誌獨立空間個數,這個參數的範圍爲0-128。
默認值爲0表示不開啓獨立的回滾日誌,且回滾日誌存儲在ibdata文件中。
這個參數是在初始化數據庫時指定的,實例一旦建立這個參數是不能改動的。
若是設置的innodb_undo_tablespaces 值大於實例建立時的個數,則會啓動失敗。
1. 重作日誌(redo log)(持久性實現原理)
事務的持久性就是經過重作日誌來實現的。
當提交事務以後,並非直接修改數據庫的數據的,而是先保證將相關的操做記錄到redo日誌中。
數據庫會根據相應的機制將內存的中的髒頁數據刷新到磁盤中。
上圖是一個簡單的重作日誌寫入流程。
在上圖中提到倆個陌生概念,Buffer pool、redo log buffer,這個倆個都是Innodb存儲引擎的內存區域的一部分。
而redo log file是位於磁盤位置。
也就說當有DML(insert、update、delete)操做時,數據會先寫入Buffer pool,而後在寫到重作日誌緩衝區。
重作日誌緩衝區會根據刷盤機制來進行寫入重作日誌中。
這個機制的設置參數爲innodb_flush_log_at_trx_commit
,參數分別爲0,1,2
上圖即爲重作日誌的寫入策略。
- 當這個參數的值爲0的時,提交事務以後,會把數據存放到redo log buffer中,而後每秒將數據寫進磁盤文件
- 當這個參數的值爲1的時,提交事務以後,就必須把redo log buffer從內存刷入到磁盤文件裏去,只要事務提交成功,那麼redo log就必然在磁盤裏了。
- 當這個參數的值爲2的狀況,提交事務以後,把redo log buffer日誌寫入磁盤文件對應的os cache緩存裏去,而不是直接進入磁盤文件,1秒後纔會把os cache裏的數據寫入到磁盤文件裏去。
2. 服務器異常中止對事務如何應對(事務寫入過程)
- 當參數爲0時,前一秒的日誌都保存在日誌緩衝區,也就是內存上,若是機器宕掉,可能丟失1秒的事務數據。
- 當參數爲1時,數據庫對IO的要求就很是高了,若是底層的硬件提供的IOPS比較差,那麼MySQL數據庫的併發很快就會因爲硬件IO的問題而沒法提高。
- 當參數爲2時,數據是直接寫進了os cache緩存,這部分屬於操做系統部分,若是操做系統部分損壞或者斷電的狀況會丟失1秒內的事務數據,這種策略相對於第一種就安全了不少,而且對IO要求也沒有那麼高。
小結
關於性能:0>2>1
關於安全:1>2>0
根據以上結論,因此說在MySQL數據庫中,刷盤策略默認值爲1,保證事務提交以後,數據絕對不會丟失。
3. 回滾日誌(undo log)(原子性實現原理)
回滾日誌保證了事務的原子性。
回滾日誌相對重作日誌來講沒有那麼複雜的流程。
當事務對數據庫進行修改時,Innodb引擎不只會記錄redo log日誌,還會記錄undo log日誌。
若是事務失敗,或者執行了rollback,爲了保證事務的原子性,就必須利用undo log日誌來進行回滾操做。
回滾日誌的存儲形式以下。
在undo log日誌文件,事務中使用的每條insert都對應了一條delete,每條update也都對應一條相反的update語句
注意:
系統發生宕機或者數據庫進程直接被殺死。
當用戶再次啓動數據庫進程時,還可以馬上經過查詢回滾日誌將以前未完成的事務進程回滾。
這也就須要回滾日誌必須先於數據持久化到磁盤上,是須要先寫日誌後寫數據庫的主要緣由。
回滾日誌不只僅能夠保證事務的原子性,仍是實現mvcc的重要因素。
以上就是關於事務的倆大日誌,重作日誌、回滾日誌的理解。
5、鎖機制
鎖在MySQL中是是很是重要的一部分,鎖對MySQL數據訪問併發有着舉足輕重的做用。
因此說鎖的內容以及細節是十分繁瑣的,本節只是對Innodb鎖的一個大概整理。
MySQL中有三類鎖,分別爲行鎖、表鎖、頁鎖。
首先須要明確的是這三類鎖是是歸屬於那種存儲引擎的。
- 行鎖:Innodb存儲引擎
- 表鎖:Myisam、MEMORY存儲引擎
- 頁鎖:BDB存儲引擎
1. 行鎖
行鎖又分爲共享鎖、排它鎖,也被稱之爲讀鎖、寫鎖,Innodb存儲引擎的默認鎖。
共享鎖(S):
假設一個事務對數據A加了共享鎖(S),則這個事務只能讀A的數據。
其它事務只能再對數據A添加共享鎖(S),而不能添加排它鎖(X),直到這個事務釋放了數據A的共享鎖(S)。
這就保證了其它事務也能夠讀取A的數據,可是在這個事務沒有釋放在A數據上的共享鎖(S)以前不能對A作任何修改。
排它鎖(X)
假設一個事務對數據A添加了排它鎖(X),則只容許這個事務讀取和修改數據A。
其它任何事務都不能在對數據A添加任何類型的鎖,直至這個事務釋放了數據A上的鎖。
排它鎖阻止其它事務獲取相同數據的共享鎖(S)、排它鎖(X),直至釋放排它鎖(X)。
特色
- 只針對單一數據進行加鎖
- 開銷大
- 加鎖慢
- 會出現死鎖
- 鎖粒度最小,發生鎖衝突的機率越低,併發越高。
還記得在上文中提到的事務併發帶來的問題、髒讀、不可重讀讀、幻讀。
學習到了這裏,應該就明白可重複讀(Repeatable Read)
如何解決髒讀、不可重讀讀了。
髒讀、和不可重複讀的解決方案很簡單,寫前加排它鎖(X),事務結束才釋放,讀前加共享鎖(S),事務結束就釋放
2. 表鎖
表鎖又分爲表共享讀鎖、表獨佔寫鎖,也被稱之爲讀鎖、寫鎖,Myisa存儲引擎的默認鎖。
- 表共享讀鎖 : 針對同一個份數據,能夠同時讀取互不影響,但不容許寫操做。
- 表獨佔寫鎖 :當寫操做沒有結束時,會阻塞全部讀和寫。
特色
- 對整張表加鎖
- 開銷小
- 加鎖快
- 無死鎖
- 鎖粒度最大,發生鎖衝突的機率越大,併發越小。
本文主要說明Innodb和Myisam的鎖,頁鎖不就不作詳細說明了。
3. 如何加鎖
表鎖
- 隱式加鎖:默認自動加鎖釋放鎖,select加讀鎖、update、insert、delete加寫鎖。
- 手動加鎖:lock table tableName read;(添加讀鎖)、lock table tableName write(添加寫鎖)。
- 手動解鎖:unlock table tableName(釋放單表)、unlock table(釋放全部表)
行鎖
- 隱式加鎖:默認自動加鎖釋放鎖,只有select不會加鎖,update、insert、delete加排它鎖。
- 手動加共享鎖:select id name from user lock in share mode;
- 手動加排它鎖:select id name form user for update;
- 解鎖:正常提交事務(commit)、事務回滾(rollback)、kill進程。
6、總結
本文主要對事務的重點知識點進行解讀,內容總結。
事務四大特徵實現原理
- 原子性:使用事務日誌的回滾日誌(undo log)實現
- 隔離性:使用mvcc實現(幻讀問題除外)
- 持久性:使用事務日誌的重作日誌(redo log)實現
- 一致性:是事務追求的最終目標,原子性、隔離性、持久性都是爲了保證數據庫一致性而存在
事務併發出現問題的區別
- 髒讀與不可重複讀的區別:髒讀是讀取沒有提交事務的數據、不可重複讀讀取的是已提交事務的數據。
- 幻讀與不可重複讀的區別:都是讀取的已提交事務的數據(與髒讀不一樣),幻讀針對的是一批數據,例如個數。不可重複讀針對的是單一數據。
事務日誌
- 重作日誌(redo log):實現了事務的持久性,提交事務後不是直接修改數據庫,而是保證每次事務操做讀寫入redo log中。而且落盤會有三種策略(詳細看四-1節)。
- 回滾日誌(undo log):實現了事務的原子性,針對DML的操做,都會有記錄相反的DML操做。
堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。但願在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。