Mysql事務(wù)

一、事務(wù)概述

我們可以將事務(wù)理解為一組sql語句的集合。事務(wù)可以只包含一條sql,也可以包含多條sql,事務(wù)中所有sql語句被當(dāng)作一個操作單元,要么全部執(zhí)行成功,要么全部失敗。
mysql中 innodb存儲引擎支持事務(wù),MyISAM不支持事務(wù)。并且Innodb存儲引擎的事務(wù)完全符合ACID特性。
ACID的理論基礎(chǔ):
1、原子性:atomicity (整個事務(wù)中的所有操作要么全部成功,要么全部失敗回滾到最初狀態(tài))
2、一致性:consistency (數(shù)據(jù)庫從一個一致性性狀態(tài)轉(zhuǎn)為另一個一直性狀態(tài))
3、隔離性:isolation (一個事務(wù)在提交之前所作出的操作是否能被其他事務(wù)可見,不同場景需求不同,因此有多個隔離級別)
4、持久性:durability (事務(wù)一旦提交,事務(wù)所做出的修改將會永久保存,即使此時數(shù)據(jù)庫崩潰,修改數(shù)據(jù)也不會消失)

在講事務(wù)例子時多數(shù)都以銀行轉(zhuǎn)賬作為場景

例子:A賬戶余額 2000,B賬戶余額 1000 ,現(xiàn)在A用戶要向B用戶轉(zhuǎn)賬200元,那么轉(zhuǎn)賬之后A賬戶余額應(yīng)該有1800,B賬戶余額應(yīng)該有1200。這種情況看似沒有問題,但是如果不用事務(wù)可能會出現(xiàn) A賬戶少了200,但是B賬戶并沒有加上200。
使用事務(wù)后整個過程如下:
事務(wù)開始
update A賬戶余額-200
update B賬戶余額+200
提交事務(wù) (事務(wù)結(jié)束)

事務(wù)底層是怎樣實現(xiàn)的呢? 通過 “事務(wù)日志” 來實現(xiàn)的這種功能。事務(wù)日志分為 redo log 和 undo log 。

(1)、redo log
mysql會將事務(wù)中的sql語句涉及到的所有數(shù)據(jù)操作先記錄到redo log中,然后再將操作從redo log中同步到對應(yīng)數(shù)據(jù)文件中。也就是說,在事務(wù)執(zhí)行提交成功之前,在修改對應(yīng)的數(shù)據(jù)文件中的記錄之前,一定要保證對應(yīng)的所有修改操作已經(jīng)記錄到 redo log中。假設(shè)事務(wù)中的sql語句涉及到10條記錄的修改,那么在修改這10條記錄之前,要將這10條修改操作記錄記錄到 redo log中,當(dāng)這10條操作都記錄到 redo log中以后再從redo log中一條一條同步到數(shù)據(jù)文件的對應(yīng)記錄中。所以即使數(shù)據(jù)文件中的數(shù)據(jù)被修改到一半時被打斷 (比如斷電),那么在恢復(fù)后也能依靠redo log日志將剩余部分操作再次同步到對應(yīng)的數(shù)據(jù)文件中。

使用redo log,能夠?qū)崿F(xiàn)ACID中的原子性。redo log 其實由兩部分組成:redo log buffer(重做日志緩沖)和 redo log file (重做日志文件) redo log buffer存在于內(nèi)存之中,容易丟失,redo log file 是持久的,存在于硬盤上。
--------------> log buffer --------------> os buffer----------------->redo log file ----------------------->數(shù)據(jù)文件
重做日志先被寫入到 redo log buffer 中,雖然內(nèi)存的速度極快,但是無法滿足持久性的需求,因為內(nèi)存數(shù)據(jù)容易丟失,所以為了滿足持久性,需要將redo log buffer中的日志寫入到redo log file 中,相當(dāng)于從內(nèi)存同步到磁盤,所以磁盤的性能會影響事務(wù)的性能,由于 redo log file 是磁盤上一系列連續(xù)的空間,所以相對于離散的空間寫速度還是比較快,當(dāng)操作被記錄到 redo log file中以后再從 redo logfile中將操作同步到數(shù)據(jù)文件中。
雖然,我們應(yīng)該實時將 redo log buffer 中的數(shù)據(jù)寫入到 redo log file 中以保證數(shù)據(jù)的安全性,但是這樣會極大的降低性能,我們可以通過設(shè)置 innodb_flush_log_at_trx_commit 參數(shù)來修改 redo log buffer 寫入 redo log file的策略,但是這樣會喪失持久性,有可能丟失部分?jǐn)?shù)據(jù),具體怎么選擇,需要根據(jù)不同的業(yè)務(wù)場景自己權(quán)衡利弊。

redo log 是物理日志,之所以說是物理日志,是因為 redo log 中記錄的是數(shù)據(jù)庫對頁的操作,而不是邏輯上的增刪改查,重做日志具有冪等性。

(2)、undo log
我們可以把undo log理解成數(shù)據(jù)被修改前的備份,如果說事務(wù)進行了一半,有一條sql沒有執(zhí)行成功,那么數(shù)據(jù)庫可以根據(jù)undo log進行撤銷,將所有修改過的數(shù)據(jù)從邏輯上恢復(fù)到修改之前的樣子,注意是邏輯上還原成原來的樣子。比如 insert了200條數(shù)據(jù),那么就需要 delete這200條。所以說 undo log 是邏輯日志,與 redo log 記錄的頁操作物理日志不同。

(3)、log group
log group位重做日志組,一個重做日志組 (log group) 中有多個重做日志文件 (redo log file),當(dāng)日志組中的第一個logfile被寫滿,則會開始將 redo log 寫入日志組中的下一個重做日志文件中,以此類推,當(dāng)日志組中的所有 redo log file 都被寫滿,則將redo log再寫入到第一個redo log file中,覆蓋原來的 redo log,以便新的redo log被寫入。
如果重做日志所在的設(shè)備崩潰了,那么 redo log將有可能丟失,這樣就無法保證 redo log 在任何時候都是可用的,所以,log group 還支持日志組鏡像,為了保險起見,我們應(yīng)該將 log group放在有冗余能力的設(shè)備上,比如 raid1。

mysql中,innodb存儲引擎支持事務(wù),myisam存儲引擎是不支持事務(wù)的,不管是redo log還是 undo log,都是innodb的產(chǎn)物。而在mysql中還有另外一種重要的日志,二進制日志,也就是平常所說的 binlog,他是建立mysql主從復(fù)制環(huán)境時所必須的日志,但是binlog并不是innodb存儲引擎層面的產(chǎn)物,而是整個mysql數(shù)據(jù)庫層面的產(chǎn)物,mysql任何存儲引擎對于數(shù)據(jù)庫的更改都會產(chǎn)生二進制日志 (binlog) 。

innodb的 redo log 記錄的是物理格式的日志,記錄了對頁的操作,而binlog記錄的是邏輯日志,記錄對應(yīng)的sql
其實不管是redolog 還是 undo log 都可以理解成恢復(fù)數(shù)據(jù)庫的手段。

二、事務(wù)日志參數(shù)

當(dāng)使用innodb存儲引擎時,我們可以通過如下語句查看日志配置參數(shù):show global variables like '%innodb%log%';


事務(wù)日志參數(shù).png

innodb_log_file_size: 表示redo log file 的大小,單位為字節(jié),上圖中的設(shè)置表示每個重做日志文件的大小為 5M
innodb_log_files_in_group :表示每個重做日志組中有幾個 redo log file
innodb_log_group_home_dir:表示重做日志組文件所在路徑。默認(rèn)情況下為 /var/lib/mysql,windows系統(tǒng)下會在data目錄下,在這個目錄下有ib_logfile0與ib_logfile1即為日志組中兩個重要日志,這兩個日志文件大小為5M,對應(yīng)了innodb_log_file_size的值。
innodb_mirrored_log_groups:表示一共有幾個日志組。如果設(shè)置為1表示沒有冗余的鏡像日志組。注意,如果重做日志所在的硬件設(shè)備并沒有冗余能力,同時用戶對數(shù)據(jù)安全性比較高,那么往往需要將此值設(shè)置為大于等于2的值。
innodb_flush_log_at_trx_commit:表示當(dāng)事務(wù)提交以后,是否立即將redo log 從內(nèi)存 (log buffer) 刷寫到redo log file 中。
如果此值設(shè)置為1 (默認(rèn)值),表示事務(wù)提交時必須將 redo log 從 log buffer 中刷寫到redologfile (磁盤)中,過程為:事務(wù)提交--log buffer---os buffer-- log file,此值為1時完全滿足ACID的要求。
如果此值設(shè)置為0,事務(wù)提交時不會將 redo log 從 log buffer刷寫到 redo log file,但是會在每秒鐘自動刷寫一次,也就是說如果mysql數(shù)據(jù)庫崩潰,最多會丟失1秒的redo log。
如果此值設(shè)置為2,當(dāng)事務(wù)提交時,redolog存在于 log buffer和os buffer中,每秒從 os buffer中刷寫到log file中一次,這種情況下如果mysql宕機,操作系統(tǒng)沒有宕機時,并不會丟失數(shù)據(jù),所以可靠性要比設(shè)置為0時要高一些。
理論上來說:此值設(shè)置為1,安全性最高,性能最低。設(shè)置為0,性能最高,安全性最低。設(shè)置為2,性能較高,安全性較低。此值為1時,能夠滿足ACID特性,其他兩個值則不滿足ACID。
我們可以根據(jù)自己的需要,設(shè)置重做日志的相關(guān)參數(shù)。

1、事務(wù)控制語句
在mysql中,默認(rèn)情況下我們每執(zhí)行一條sql語句,mysql都會把這條sql當(dāng)做一個單語句事務(wù)進行提交,并且是默認(rèn)自動提交的。我們可以使用如下語句查看:
show global variables like '%autocommit%';
show session variables like '%autocommit%';


image.png

如上圖所示,默認(rèn)情況下,autocommit是開啟的,表示事務(wù)都是自動提交的。
如果需要手動控制提交,需要顯示的開啟一個事務(wù),或者禁用自動提交功能 (set autocommit=0),進行手動提交。

(1)、start transaction 或者 begin :表示顯示的開啟一個事務(wù),在存儲過程中不能用begin開啟一個事務(wù)。
(2)、commit 或者 commit work:表示提交事務(wù),也就是從begin到commit之間所有sql語句對數(shù)據(jù)庫所做的修改會被真正執(zhí)行
(3)、rollback 或者 rollback work:表示回滾事務(wù),回滾事務(wù)會撤銷所有未提交的修改并結(jié)束當(dāng)前事務(wù)。注意,使用rollback回滾事務(wù)以后,當(dāng)前事務(wù)會結(jié)束,后面的操作不算在當(dāng)前事務(wù)以內(nèi)。
(4)、savepoint 標(biāo)識符:表示創(chuàng)建一個事務(wù)保存點,以便我們回滾到當(dāng)前保存點,而不是回滾整個事務(wù)。
(5)、rollback to savepoint 標(biāo)識符:表示根據(jù)標(biāo)識符回滾到指定的保存點,使用rollback to savepoint只會撤銷對應(yīng)保存點之后的操作,并且不會結(jié)束當(dāng)前事務(wù),回滾到指定的保存點以后的操作仍然屬于當(dāng)前事務(wù),于rollback不同。
(6)、release savepoint 標(biāo)識符:表示刪除一個保存點。

下面進行實際操作,演示一下效果:


image.png

先從最簡單事務(wù)開始:


事務(wù)正常提交.png

下面演示事務(wù)回滾的例子:


事務(wù)正?;貪L.png

事務(wù)開始之前,user表中有5條數(shù)據(jù),事務(wù)開始以后我們刪除id為 4和5的兩條記錄,但是我們并沒有commit,所以這些操作沒有真正持久化到數(shù)據(jù)庫中,此時,我們執(zhí)行了rollback,所有未提交的操作都被撤銷了,同時當(dāng)前事務(wù)結(jié)束。

上面演示了事務(wù)的正常提交和回滾,下面演示一下保存點。

事務(wù)保存點1.png

事務(wù)保存點2.png

通過事務(wù)控制語句,即可顯示的手動的對事務(wù)進行控制,之前說過,我們可以禁用autocommit功能,從而進行手動的提交操作,示例如下:set @@session.autocommit=0;


關(guān)閉事務(wù)自動提交.png

因為關(guān)閉了自動提交功能,所以,每個sql語句并不會自動被當(dāng)做一個單語句事務(wù),所以每個sql語句并不會被自動提交。如果需要將之前的修改持久化,需要手動執(zhí)行commit操作。

三、事務(wù)的隔離級別

1、事務(wù)隔離級別綜述

mysql中,innodb所提供的事務(wù)復(fù)合ACID的要求,而通過事務(wù)日志中的 redo log和undo log 滿足了原子性、一致性、持久性、事務(wù)還會通過鎖機制滿足隔離性,在innodb存儲引擎中,有不同的隔離級別,他們有著不同的隔離性。
下面使用兩個會話窗口演示隔離級別

image.png

image.png

此處,我們列出innodb中事務(wù)的所有隔離級別,然后逐個了解他們,事務(wù)隔離級別一共有如下四種:
1、READ-UNCOMMITTED:此隔離級別翻譯為:“讀未提交”
2、READ-COMMITTED:此隔離級別翻譯為:“讀已提交”
3、REPEATABLE-READ:此隔離級別翻譯為:“可重復(fù)讀”
4、SERIALIZABLE:此隔離級別翻譯為:“串行化”

Mysql的默認(rèn)隔離級別為REPEATABLE-READ,即 “可重復(fù)讀”
查看mysql的隔離級別:show variables like 'tx_isolation';

image.png

如果需要修改配置文件 my.cnf配置文件,則可通過如下參數(shù)配置mysql的事務(wù)隔離級別
transaction_isolation=REPEATABLE-READ

下面詳細(xì)介紹各個隔離界別

1、可重讀

我們先來總結(jié)一下可重讀隔離級別的特性。仍然以上面例子為例,在事務(wù)1中修改了一條數(shù)據(jù)以后,事務(wù)2看到的數(shù)據(jù)仍然是事務(wù)1修改之前的數(shù)據(jù),即使事務(wù)1提交了,在事務(wù)2沒有提交之前,事務(wù)2看到的數(shù)據(jù)還是相同的,所以這種隔離級別被稱為“可重讀”

但是你可能會有個問題,之前說過,事務(wù)的隔離性是由鎖來實現(xiàn)的,那么,當(dāng)事務(wù)1執(zhí)行更新語句時,事務(wù)1應(yīng)該對數(shù)據(jù)加了寫鎖,但是在事務(wù)2中,仍然可以進行查詢操作,即進行讀操作,可是寫鎖是排他鎖,在事務(wù)1已經(jīng)添加寫鎖的情況下,為什么事務(wù)2還可以讀取呢?這是因為innodb采用了一致性非鎖定讀的機制提高了數(shù)據(jù)庫并發(fā)性。一致性非鎖定讀表示如果當(dāng)前行如果被施加了排他鎖,那么當(dāng)需要讀取行數(shù)據(jù)時,則不會等待行上的鎖的釋放,而是會去讀取一個快照數(shù)據(jù)。

一行記錄可能有不止一個快照數(shù)據(jù),并不是所有隔離級別都使用了一致性非鎖定讀,在“可重讀”和“讀提交”的隔離級別下,innodb存儲引擎使用了一致性非鎖定讀,但是在這兩個隔離級別中,對于快照的定義也不相同。在“可重讀”隔離級別下,快照數(shù)據(jù)是當(dāng)前事務(wù)開始的樣子,但是在“讀提交”的隔離級別下,由于快照定義不同,所以顯示的現(xiàn)象也不同。

在可重讀隔離級別下,可能會出現(xiàn)“幻讀”的問題,那么什么是幻讀,我們一起看一下


image.png

從上圖可以看出,事務(wù)1commit之后,數(shù)據(jù)其實就已經(jīng)發(fā)生了變化,但是事務(wù)2在沒有update之前無法看到變化的數(shù)據(jù),但是當(dāng)事務(wù)2更新數(shù)據(jù)以后,發(fā)現(xiàn)莫名多出了一條數(shù)據(jù)。在同一個事務(wù)中,執(zhí)行兩次同樣的sql,第二次sql返回之前不存在的行,或者之前出現(xiàn)的數(shù)據(jù)不見了,這種現(xiàn)象被稱之為“幻讀”。
注意:事務(wù)2中的update語句并沒有指定任何條件,相當(dāng)于更新所有行的對應(yīng)字段,如果指定了條件,并沒有更新到“隱藏”行,那么可能無法看到幻讀現(xiàn)象
上述事務(wù)2還沒有進行commit操作,如果不執(zhí)行commit操作,那么所有數(shù)據(jù)都會回滾。也就是事務(wù)2還沒有生效。

2、串行化

經(jīng)過上述實例我們可以發(fā)現(xiàn),事務(wù)處于“REPEATABLE-READ”可重讀級別時,會出現(xiàn)幻讀的情況,在之前我們提到,不同的隔離級別所引入的隔離性不同,那么有沒有一種隔離級別能夠解決幻讀的問題呢?答案是:串行化可以解決這個問題。我們來試試串行化時,事務(wù)是怎么樣工作的。

image.png

如上圖,將兩個會話窗口中的事務(wù)隔離級別設(shè)置為串行化后,分別開啟兩個事務(wù),在事務(wù)1插入一條數(shù)據(jù)沒有提交的情況下,此時事務(wù)2執(zhí)行查詢數(shù)據(jù)時好像是被卡主了,沒有任何反應(yīng)

過一段時間后,會報一個錯:獲取鎖失敗


image.png

換另外一種實驗方式:在事務(wù)2被阻塞的時候,提交事務(wù)1,會發(fā)現(xiàn)事務(wù)2查詢立即返回結(jié)果。

image.png

從上述實驗來看,當(dāng)事務(wù)處于串行化隔離級別時,是不可能出現(xiàn)幻讀的情況的,因為如果另一個事務(wù)對表添加了寫鎖,那么在當(dāng)前事務(wù)是無法讀取到數(shù)據(jù)的,必須等到另一個事務(wù)提交或者回滾。使用串行化隔離級別不會出現(xiàn)幻讀,但是數(shù)據(jù)庫失去了并發(fā)的能力,所以我們很少將隔離級別設(shè)置為串行化,因為這種隔離性過于嚴(yán)格。

3、讀已提交

現(xiàn)在我們已經(jīng)了解了兩種隔離級別,可重讀與串行化。串行化隔離級別的隔離性是最強的,沒有并發(fā)能力,可重讀隔離級別的隔離性稍微次之,但是比較串行化而言,并發(fā)能力較好,不過存在“幻讀”的問題。

image.png

同樣在兩個會話中開啟兩個事務(wù),在事務(wù)1中修改數(shù)據(jù),此時事務(wù)1還未提交,在事務(wù)2中并不能看到事務(wù)1中的修改,而當(dāng)事務(wù)1提交以后,事務(wù)2中即可看到事務(wù)1中的修改,話句話說,就是事務(wù)2能夠讀到事務(wù)1提交后的更改,這種隔離級別被稱為“讀提交”。

在“讀提交”的隔離級別下,也會出現(xiàn)“幻讀”的問題,示例如下:

image.png

在上述示例中,事務(wù)1向表中插入了一行數(shù)據(jù),在事務(wù)1提交以后,事務(wù)2中即可看到,但是事務(wù)2還沒有提交,在事務(wù)2中執(zhí)行兩次相同的查詢語句,莫名其妙的多出了一行,出現(xiàn)了“幻讀”的情況。

在讀已提交隔離級別下,除了會出現(xiàn)幻讀的情況,還會出現(xiàn)不可重讀的情況?!安豢芍刈x”表示“不一定可重讀”,不要理解為“一定不可重讀”。


image.png

上述例子中,事務(wù)1中修改了id=1的數(shù)據(jù),提交以后,事務(wù)2中不能再讀到之前的數(shù)據(jù)了,所以出現(xiàn)了“不可重讀”的現(xiàn)象。
其實,總結(jié)一下這個隔離級別,是因為事務(wù)1的提交對事務(wù)2立即可見,所造成的“不可重讀”和“幻讀”的情況。

那么我們再來總結(jié)一下“讀提交”這個隔離級別,在“讀提交”隔離級別下,會出現(xiàn)“不可重讀”,“幻讀”的問題,比“可重讀”隔離級別問題更多,但是并發(fā)能力更強?,F(xiàn)在就剩下一個隔離級別沒有了解到,那就是“讀未提交”。

4、讀未提交

還是以一個例子演示:


image.png

將隔離級別調(diào)整為“讀未提交”,可以看出事務(wù)1并沒有提交,但是事務(wù)2看到了事務(wù)1中鎖做出的修改。
當(dāng)事務(wù)能夠看到別的事務(wù)中“未提交”的數(shù)據(jù),我們稱這種現(xiàn)象為“臟讀”。
上例中,事務(wù)1并未提交,但是其所作出的修改已經(jīng)能在事務(wù)2中查看到,由于事務(wù)1中的修改可能被回滾,或者繼續(xù)被修改,所以事務(wù)2中看到的數(shù)據(jù)飄忽不定,并不是最終的數(shù)據(jù),所以是“臟的”。在事務(wù)隔離級別為“讀未提交”時,其并發(fā)性能最強,但是其隔離性與安全性是最差的。

因此,在事務(wù)處于“讀未提交”這種隔離級別時,會出現(xiàn)“臟讀”,同時也會出現(xiàn)“不可重讀”,“幻讀”的情況。

四、臟讀、幻讀、不可重讀的區(qū)別

我們來總結(jié)一下它們之間的區(qū)別:
臟讀:當(dāng)前事務(wù)可以查看到其他事務(wù)未提交的數(shù)據(jù)(側(cè)重點在于別的事務(wù)未提交)
幻讀:幻讀的表象于不可重讀的表象都讓人“懵逼”,很容易搞混,但是如果非要細(xì)分,幻讀的側(cè)重點在于新增和刪除。表示在同一個事務(wù)中,使用相同的查詢語句,第二次查詢時,莫名的多出了一些之前不存在的數(shù)據(jù),或者莫名的不見了一些數(shù)據(jù)。
不可重讀:不可重讀的側(cè)重點在于更新數(shù)據(jù)。表示在同一事務(wù)中,查詢相同的數(shù)據(jù)范圍時,同一個數(shù)據(jù)資源莫名的改變了。

五、不同隔離級別所擁有的問題

下面做一個最終的總結(jié):


image.png
?著作權(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)容