一篇文章帶你輕松了解MySQL之事務(wù)的簡(jiǎn)介
前言:
事務(wù)(Transaction
)是一組SQL組成的執(zhí)行單元(Unit),是數(shù)據(jù)庫(kù)并發(fā)控制和恢復(fù)回滾的基本單位。一個(gè)事務(wù)中可能包含多個(gè)SQL,要么都失敗,要么都成功,今天我們就詳細(xì)學(xué)習(xí)一下。
一、事務(wù)的起源
對(duì)于大部分程序員來(lái)說(shuō),他們的任務(wù)就是把現(xiàn)實(shí)世界的業(yè)務(wù)場(chǎng)景映射到數(shù)據(jù)庫(kù)世界。比如銀行為了存儲(chǔ)人們的賬戶信息會(huì)建立一個(gè)account
表:
mysql> CREATE TABLE account ( id INT NOT NULL AUTO_INCREMENT COMMENT '自增id', name VARCHAR(100) COMMENT '客戶名稱', balance INT COMMENT '余額', PRIMARY KEY (id) ); Query OK, 0 rows affected (0.04 sec)
張三和李四是一對(duì)好基友,他們都到銀行開(kāi)一個(gè)賬戶,他們?cè)诂F(xiàn)實(shí)世界中擁有的資產(chǎn)就會(huì)體現(xiàn)在數(shù)據(jù)庫(kù)世界的account表中。比如現(xiàn)在張三有11元,李四只有2元,那么現(xiàn)實(shí)中的這個(gè)情況映射到數(shù)據(jù)庫(kù)的account表就是這樣:
mysql> INSERT INTO account(name,balance) VALUES ('張三',11),('李四',2); Query OK, 2 rows affected (0.01 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> SELECT * FROM account; +----+--------+---------+ | id | name | balance | +----+--------+---------+ | 1 | 張三 | 11 | | 2 | 李四 | 2 | +----+--------+---------+ 2 rows in set (0.00 sec)
在某個(gè)特定的時(shí)刻,張三李四這些家伙在銀行所擁有的資產(chǎn)是一個(gè)特定的值,這些特定的值也可以被描述為賬戶在這個(gè)特定的時(shí)刻現(xiàn)實(shí)世界的一個(gè)狀態(tài)。隨著時(shí)間的流逝,張三和李四可能陸續(xù)進(jìn)行向賬戶中存錢、取錢或者向別人轉(zhuǎn)賬等操作,這樣他們賬戶中的余額就可能發(fā)生變動(dòng),每一個(gè)操作都相當(dāng)于現(xiàn)實(shí)世界中賬戶的一次狀態(tài)轉(zhuǎn)換。數(shù)據(jù)庫(kù)世界作為現(xiàn)實(shí)世界的一個(gè)映射,當(dāng)然也要進(jìn)行相應(yīng)的變動(dòng)。不變不知道,一變嚇一跳,現(xiàn)實(shí)世界中一些看似很簡(jiǎn)單的狀態(tài)轉(zhuǎn)換,映射到數(shù)據(jù)庫(kù)世界卻不是那么容易的。比方說(shuō)有一次李四需要10元錢,急忙打電話給張三要借10塊錢,現(xiàn)實(shí)世界中的張三走向了ATM機(jī),輸入了李四的賬號(hào)以及10元的轉(zhuǎn)賬金額,然后按下確認(rèn),張三就拔卡走人了。對(duì)于數(shù)據(jù)庫(kù)世界來(lái)說(shuō),相當(dāng)于執(zhí)行了下邊這兩條語(yǔ)句:
mysql> UPDATE account SET balance = balance - 10 WHERE id = 1; Query OK, 1 row affected (0.03 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> UPDATE account SET balance = balance + 10 WHERE id = 2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0
但是這里頭有個(gè)問(wèn)題,上述兩條語(yǔ)句只執(zhí)行了一條時(shí)忽然服務(wù)器斷電了咋辦?把張三的錢扣了,但是沒(méi)給李四轉(zhuǎn)過(guò)去~ 即使對(duì)于單獨(dú)的一條語(yǔ)句,我們前邊嘮叨Buffer Pool
時(shí)也說(shuō)過(guò),在對(duì)某個(gè)頁(yè)面進(jìn)行讀寫(xiě)訪問(wèn)時(shí),都會(huì)先把這個(gè)頁(yè)面加載到Buffer Pool
中,之后如果修改了某個(gè)頁(yè)面,也不會(huì)立即把修改同步到磁盤(pán),而只是把這個(gè)修改了的頁(yè)面加到Buffer Pool
的flush
鏈表中,在之后的某個(gè)時(shí)間點(diǎn)才會(huì)刷新到磁盤(pán)。如果在將修改過(guò)的頁(yè)刷新到磁盤(pán)之前系統(tǒng)崩潰了那豈不是李四還是沒(méi)錢?或者在刷新磁盤(pán)的過(guò)程中(只刷新部分?jǐn)?shù)據(jù)到磁盤(pán)上)系統(tǒng)奔潰了李四也沒(méi)錢?
怎么才能保證讓可憐的李四有錢呢?其實(shí)再仔細(xì)想想,我們只是想讓某些數(shù)據(jù)庫(kù)操作符合現(xiàn)實(shí)世界中狀態(tài)轉(zhuǎn)換的規(guī)則而已,設(shè)計(jì)數(shù)據(jù)庫(kù)的大叔們仔細(xì)盤(pán)算了盤(pán)算,現(xiàn)實(shí)世界中狀態(tài)轉(zhuǎn)換的規(guī)則有好幾條,我們慢慢道來(lái)。
1.1 原子性(Atomicity)
現(xiàn)實(shí)世界中轉(zhuǎn)賬操作是一個(gè)不可分割的操作,也就是說(shuō)要么壓根就沒(méi)轉(zhuǎn),要么轉(zhuǎn)賬成功,不能存在中間的狀態(tài),也就是轉(zhuǎn)了一半的這種情況。設(shè)計(jì)數(shù)據(jù)庫(kù)的時(shí)候把這種要么全做,要么全不做的規(guī)則稱之為原子性。但是在現(xiàn)實(shí)世界中的一個(gè)不可分割的操作卻可能對(duì)應(yīng)著數(shù)據(jù)庫(kù)世界若干條不同的操作,數(shù)據(jù)庫(kù)中的一條操作也可能被分解成若干個(gè)步驟(比如先修改緩存頁(yè),之后再刷新到磁盤(pán)等),最要命的是在任何一個(gè)可能的時(shí)間都可能發(fā)生意想不到的錯(cuò)誤(可能是數(shù)據(jù)庫(kù)本身的錯(cuò)誤,或者是操作系統(tǒng)錯(cuò)誤,甚至是直接斷電之類的)而使操作執(zhí)行不下去,所以李四可能也沒(méi)錢。為了保證在數(shù)據(jù)庫(kù)世界中某些操作的原子性,設(shè)計(jì)數(shù)據(jù)庫(kù)的時(shí)候需要費(fèi)一些心機(jī)來(lái)保證如果在執(zhí)行操作的過(guò)程中發(fā)生了錯(cuò)誤,把已經(jīng)做了的操作恢復(fù)成沒(méi)執(zhí)行之前的樣子,這也是我們后邊章節(jié)要仔細(xì)嘮叨的內(nèi)容。
1.2 隔離性(Isolation)
現(xiàn)實(shí)世界中的兩次狀態(tài)轉(zhuǎn)換應(yīng)該是互不影響的,比如說(shuō)張三向李四同時(shí)進(jìn)行的兩次金額為5元的轉(zhuǎn)賬(假設(shè)可以在兩個(gè)ATM機(jī)上同時(shí)操作)。那么最后張三的賬戶里肯定會(huì)少10元,李四的賬戶里肯定多了10元。但是到對(duì)應(yīng)的數(shù)據(jù)庫(kù)世界中,事情又變的復(fù)雜了一些。為了簡(jiǎn)化問(wèn)題,我們粗略的假設(shè)張三向李四轉(zhuǎn)賬5元的過(guò)程是由下邊幾個(gè)步驟組成的:
- 步驟一:讀取張三賬戶的余額到變量A中,這一步驟簡(jiǎn)寫(xiě)為read(A)
- 步驟二:將張三賬戶的余額減去轉(zhuǎn)賬金額,這一步驟簡(jiǎn)寫(xiě)為A = A - 5
- 步驟三:將張三賬戶修改過(guò)的余額寫(xiě)到磁盤(pán)里,這一步驟簡(jiǎn)寫(xiě)為write(A)
- 步驟四:讀取李四賬戶的余額到變量B,這一步驟簡(jiǎn)寫(xiě)為read(B)
- 步驟五:將李四賬戶的余額加上轉(zhuǎn)賬金額,這一步驟簡(jiǎn)寫(xiě)為B = B + 5
- 步驟六:將李四賬戶修改過(guò)的余額寫(xiě)到磁盤(pán)里,這一步驟簡(jiǎn)寫(xiě)為write(B)
我們將張三向李四同時(shí)進(jìn)行的兩次轉(zhuǎn)賬操作分別稱為T1
和T2
,在現(xiàn)實(shí)世界中T1
和T2
是應(yīng)該沒(méi)有關(guān)系的,可以先執(zhí)行完T1
,再執(zhí)行T2
,或者先執(zhí)行完T2
,再執(zhí)行T1
,對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作就像這樣:
但是很不幸,真實(shí)的數(shù)據(jù)庫(kù)中T1
和T2
的操作可能交替執(zhí)行,比如這樣:
如果按照上圖中的執(zhí)行順序來(lái)進(jìn)行兩次轉(zhuǎn)賬的話,最終張三的賬戶里還剩6
元錢,相當(dāng)于只扣了5
元錢,但是李四的賬戶里卻成了12
元錢,相當(dāng)于多了10
元錢,這銀行豈不是要虧死了?
所以對(duì)于現(xiàn)實(shí)世界中狀態(tài)轉(zhuǎn)換對(duì)應(yīng)的某些數(shù)據(jù)庫(kù)操作來(lái)說(shuō),不僅要保證這些操作以原子性的方式執(zhí)行完成,而且要保證其它的狀態(tài)轉(zhuǎn)換不會(huì)影響到本次狀態(tài)轉(zhuǎn)換,這個(gè)規(guī)則被稱之為隔離性。這時(shí)設(shè)計(jì)數(shù)據(jù)庫(kù)的大叔們就需要采取一些措施來(lái)讓訪問(wèn)相同數(shù)據(jù)(上例中的A賬戶和B賬戶)的不同狀態(tài)轉(zhuǎn)換(上例中的T1和T2)對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作的執(zhí)行順序有一定規(guī)律,這也是我們后邊章節(jié)要仔細(xì)嘮叨的內(nèi)容。
1.3 一致性(Consistency)
我們生活的這個(gè)世界存在著形形色色的約束,比如身份證號(hào)不能重復(fù),性別只能是男或者女,高考的分?jǐn)?shù)只能在0~750之間,人民幣面值最大只能是100,紅綠燈只有3種顏色,房?jī)r(jià)不能為負(fù)的,學(xué)生要聽(tīng)老師話,吧啦吧啦有點(diǎn)兒扯遠(yuǎn)了~ 只有符合這些約束的數(shù)據(jù)才是有效的,比如有個(gè)小孩兒跟你說(shuō)他高考考了1000分,你一聽(tīng)就知道他胡扯呢。數(shù)據(jù)庫(kù)世界只是現(xiàn)實(shí)世界的一個(gè)映射,現(xiàn)實(shí)世界中存在的約束當(dāng)然也要在數(shù)據(jù)庫(kù)世界中有所體現(xiàn)。如果數(shù)據(jù)庫(kù)中的數(shù)據(jù)全部符合現(xiàn)實(shí)世界中的約束(all defined rules),我們說(shuō)這些數(shù)據(jù)就是一致的,或者說(shuō)符合一致性的。
如何保證數(shù)據(jù)庫(kù)中數(shù)據(jù)的一致性(就是符合所有現(xiàn)實(shí)世界的約束)呢?這其實(shí)靠?jī)煞矫娴呐Γ?/p>
方面一: 數(shù)據(jù)庫(kù)本身能為我們保證一部分一致性需求(就是數(shù)據(jù)庫(kù)當(dāng)身可以保證一部分現(xiàn)實(shí)世界的約束永遠(yuǎn)有效)。
我們知道MySQL數(shù)據(jù)庫(kù)可以為表建立主鍵、唯一索引、外鍵、聲明某個(gè)列為NOT NULL
來(lái)拒絕NULL
值的插入。比如說(shuō)當(dāng)我們對(duì)某個(gè)列建立唯一索引時(shí),如果插入某條記錄時(shí)該列的值重復(fù)了,那么MySQL就會(huì)報(bào)錯(cuò)并且拒絕插入。除了這些我們已經(jīng)非常熟悉的保證一致性的功能,MySQL還支持CHECK語(yǔ)法來(lái)當(dāng)定義約束,比如這樣:
CREATE TABLE account ( id INT NOT NULL AUTO_INCREMENT COMMENT '自增id', name VARCHAR(100) COMMENT '客戶名稱', balance INT COMMENT '余額', PRIMARY KEY (id), CHECK (balance >= 0) );
上述例子中的CHECK
語(yǔ)句本意是想規(guī)定balance
列不能存儲(chǔ)小于0
的數(shù)字,對(duì)應(yīng)的現(xiàn)實(shí)世界的意思就是銀行賬戶余額不能小于0
。但是很遺憾,MySQL僅僅支持 CHECK語(yǔ)法,但實(shí)際上并沒(méi)有一點(diǎn)卵用,也就是說(shuō)即使我們使用上述帶有CHECK
子句的建表語(yǔ)句來(lái)創(chuàng)建account
表,那么在后續(xù)插入或更新記錄時(shí),MySQL并不會(huì)去檢查CHECK子句中的約束是否成立。
小提示:
其它的一些數(shù)據(jù)庫(kù),比如SQL Server或者Oracle支持的CHECK語(yǔ)法是有實(shí)實(shí)在在的作用的,每次進(jìn)行插入或更新記錄之前都會(huì)檢查一下數(shù)據(jù)是否符合CHECK子句中指定的約束條件是否成立,如果不成立的話就會(huì)拒絕插入或更新。
雖然CHECK
子句對(duì)一致性檢查沒(méi)什么卵用,但是我們還是可以通過(guò)定義觸發(fā)器的方式來(lái)當(dāng)定義一些約束條件以保證數(shù)據(jù)庫(kù)中數(shù)據(jù)的一致性。
方面二: 更多的一致性需求需要靠寫(xiě)業(yè)務(wù)代碼的程序員當(dāng)己保證。
為建立現(xiàn)實(shí)世界和數(shù)據(jù)庫(kù)世界的對(duì)應(yīng)關(guān)系,理論上應(yīng)該把現(xiàn)實(shí)世界中的所有約束都反應(yīng)到數(shù)據(jù)庫(kù)世界中,但是很不幸,在更改數(shù)據(jù)庫(kù)數(shù)據(jù)時(shí)進(jìn)行一致檢查是一個(gè)耗費(fèi)性能的?作,比方說(shuō)我們?yōu)?code>account表建立了一個(gè)觸發(fā)器,每當(dāng)插入或者更新記錄時(shí)都會(huì)校驗(yàn)一下balance
列的值是不是大于0,這就會(huì)影響到插入或更新的速度。僅僅是校驗(yàn)一行記錄符不符合一致性需求倒也不是什么大問(wèn)題,有的一致性需求簡(jiǎn)直變態(tài),比方說(shuō)銀行會(huì)建立一張代表賬單的表,里邊兒記錄了每個(gè)賬戶的每筆交易,每一筆交易完成后,都需要保證整個(gè)系統(tǒng)的余額等于所有賬戶的收入減去所有賬戶的支出。如果在數(shù)據(jù)庫(kù)層面實(shí)現(xiàn)這個(gè)一致性需求的話,每次發(fā)生交易時(shí),都需要將所有的收入加起來(lái)減去所有的支出,再將所有的賬戶余額加起來(lái),看看兩個(gè)值相不相等。這不是搞笑呢么,如果賬單表里有幾億條記錄,光是這個(gè)校驗(yàn)的過(guò)程可能就要跑好幾個(gè)小時(shí),也就是說(shuō)你在煎餅攤買個(gè)煎餅,使用銀行卡付款之后要等好幾個(gè)小時(shí)才能提示付款成功,這樣的性能代價(jià)是完全承受不起的。
現(xiàn)實(shí)生活中復(fù)雜的一致性需求比比皆是,而由于性能問(wèn)題把一致性需求交給數(shù)據(jù)庫(kù)去解決這是不現(xiàn)實(shí)的,所以這個(gè)鍋就甩給了業(yè)務(wù)端程序員。比方說(shuō)我們的account
表,我們也可以不建立觸發(fā)器,只要編寫(xiě)業(yè)務(wù)的程序員在當(dāng)己的業(yè)務(wù)代碼里判斷一下,當(dāng)某個(gè)操作會(huì)將balance
列的值更新為小于0的值時(shí),就不執(zhí)行該操作就好了嘛!
我們前邊嘮叨的原子性和隔離性都會(huì)對(duì)一致性產(chǎn)生影響,比如我們現(xiàn)實(shí)世界中轉(zhuǎn)賬操作完成后,有一個(gè)一致性需求就是參與轉(zhuǎn)賬的賬戶的總的余額是不變的。如果數(shù)據(jù)庫(kù)不遵循原子性要求,也就是轉(zhuǎn)了一半就不轉(zhuǎn)了,也就是說(shuō)給張三扣了錢而沒(méi)給李四轉(zhuǎn)過(guò)去,那最后就是不符合一致性需求的;類似的,如果數(shù)據(jù)庫(kù)不遵循隔離性要求,就像我們前邊嘮叨隔離性時(shí)舉的例子中所說(shuō)的,最終張三賬戶中扣的錢和李四賬戶中漲的錢可能就不一樣了,也就是說(shuō)不符合一致性需求了。所以說(shuō),數(shù)據(jù)庫(kù)某些操作的原子性和隔離性都是保證一致性的一種手段,在操作執(zhí)行完成后保證符合所有既定的約束則是一種結(jié)果。那滿足原子性和隔離性的操作一定就滿足一致性么?那倒也不一定,比如說(shuō)張三要轉(zhuǎn)賬20元給李四,雖然在滿足原子性和隔離性,但轉(zhuǎn)賬完成了之后張三的賬戶的余額就成負(fù)的了,這顯然是不滿足一致性的。那不滿足原子性和隔離性的操作就一定不滿足一致性么?這也不一定,只要最后的結(jié)果符合所有現(xiàn)實(shí)世界中的約束,那么就是符合一致性的。
1.4 持久性(Durability)
當(dāng)現(xiàn)實(shí)世界的一個(gè)狀態(tài)轉(zhuǎn)換完成后,這個(gè)轉(zhuǎn)換的結(jié)果將永久的保留,這個(gè)規(guī)則被設(shè)計(jì)數(shù)據(jù)庫(kù)的大叔們稱為持久性。比方說(shuō)張三向李四轉(zhuǎn)賬,當(dāng)ATM機(jī)提示轉(zhuǎn)賬成功了,就意味著這次賬戶的狀態(tài)轉(zhuǎn)換完成了,張三就可以拔卡走人了。如果當(dāng)張三走掉之后,銀行又把這次轉(zhuǎn)賬操作給撤銷掉,恢復(fù)到?jīng)]轉(zhuǎn)賬之前的樣子,那李四還是沒(méi)錢,所以這個(gè)持久性是非常重要的。當(dāng)把現(xiàn)實(shí)世界的狀態(tài)轉(zhuǎn)換映射到數(shù)據(jù)庫(kù)世界時(shí),持久性意味著該轉(zhuǎn)換對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作所修改的數(shù)據(jù)都應(yīng)該在磁盤(pán)上保留下來(lái),不論之后發(fā)生了什么事故,本次轉(zhuǎn)換造成的影響都不應(yīng)該被丟失掉。
二、事務(wù)的概念
為了方便大家記住我們上邊嘮叨的現(xiàn)實(shí)世界狀態(tài)轉(zhuǎn)換過(guò)程中需要遵守的4個(gè)特性,我們把原子性(Atomicity)、隔離性(Isolation)、一致性(Consistency)和持久性(Durability)這四個(gè)詞對(duì)應(yīng)的英文單詞首字母提取出來(lái)就是A、I、C、D,稍微變換一下順序可以組成一個(gè)完整的英文單詞:ACID。想必大家都是學(xué)過(guò)初高中英語(yǔ)的,ACID是英文酸的意思,以后我們提到ACID這個(gè)詞兒,大家就應(yīng)該想到原子性、一致性、隔離性、持久性這幾個(gè)規(guī)則。另外,設(shè)計(jì)數(shù)據(jù)庫(kù)的大叔為了方便起見(jiàn),把需要保證原子性、隔離性、一致性和持久性的一個(gè)或多個(gè)數(shù)據(jù)庫(kù)操作稱之為一個(gè)事務(wù)(英文名是:transaction)。
我們現(xiàn)在知道事務(wù)是一個(gè)抽象的概念,它其實(shí)對(duì)應(yīng)著一個(gè)或多個(gè)數(shù)據(jù)庫(kù)操作,設(shè)計(jì)數(shù)據(jù)庫(kù)的大叔根據(jù)這些操作所執(zhí)行的不同階段把事務(wù)大致上劃分成了這么幾個(gè)狀態(tài):
- 活動(dòng)的(active)
事務(wù)對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作正在執(zhí)行過(guò)程中時(shí),我們就說(shuō)該事務(wù)處在活動(dòng)的狀態(tài)。 - 部分提交的(partially committed)
當(dāng)事務(wù)中的最后一個(gè)操作執(zhí)行完成,但由于操作都在內(nèi)存中執(zhí)行,所造成的影響并沒(méi)有刷新到磁盤(pán)時(shí),我們就說(shuō)該事務(wù)處在部分提交的狀態(tài)。 - 失敗的(failed)
當(dāng)事務(wù)處在活動(dòng)的或者部分提交的狀態(tài)時(shí),可能遇到了某些錯(cuò)誤(數(shù)據(jù)庫(kù)當(dāng)身的錯(cuò)誤、操作系統(tǒng)錯(cuò)誤或者直接斷電等)而無(wú)法繼續(xù)執(zhí)行,或者人為的停止當(dāng)前事務(wù)的執(zhí)行,我們就說(shuō)該事務(wù)處在失敗的狀態(tài)。 - 中止的(aborted)
如果事務(wù)執(zhí)行了半截而變?yōu)槭〉臓顟B(tài),比如我們前邊嘮叨的張三向李四轉(zhuǎn)賬的事務(wù),當(dāng)張三賬戶的錢被扣除,但是李四賬戶的錢沒(méi)有增加時(shí)遇到了錯(cuò)誤,從而當(dāng)前事務(wù)處在了失敗的狀態(tài),那么就需要把已經(jīng)修改的張三賬戶余額調(diào)整為未轉(zhuǎn)賬之前的金額,換句話說(shuō),就是要撤銷失敗事務(wù)對(duì)當(dāng)前數(shù)據(jù)庫(kù)造成的影響。書(shū)面一點(diǎn)的話,我們把這個(gè)撤銷的過(guò)程稱之為回滾。當(dāng)回滾操作執(zhí)行完畢時(shí),也就是數(shù)據(jù)庫(kù)恢復(fù)到了執(zhí)行事務(wù)之前的狀態(tài),我們就說(shuō)該事務(wù)處在了中止的狀態(tài)。 - 提交的(committed)
當(dāng)一個(gè)處在部分提交的狀態(tài)的事務(wù)將修改過(guò)的數(shù)據(jù)都同步到磁盤(pán)上之后,我們就可以說(shuō)該事務(wù)處在了提交的狀態(tài)。
隨著事務(wù)對(duì)應(yīng)的數(shù)據(jù)庫(kù)操作執(zhí)行到不同階段,事務(wù)的狀態(tài)也在不斷變化,一個(gè)基本的狀態(tài)轉(zhuǎn)換圖如下所示:
從圖中大家也可以看出了,只有當(dāng)事務(wù)處于提交的或者中止的狀態(tài)時(shí),一個(gè)事務(wù)的生命周期才算是結(jié)束了。對(duì)于已經(jīng)提交的事務(wù)來(lái)說(shuō),該事務(wù)對(duì)數(shù)據(jù)庫(kù)所做的修改將永久生效,對(duì)于處于中止?fàn)顟B(tài)的事務(wù),該事務(wù)對(duì)數(shù)據(jù)庫(kù)所做的所有修改都會(huì)被回滾到?jīng)]執(zhí)行該事務(wù)之前的狀態(tài)。
小提示:
大家知道我們的計(jì)算機(jī)術(shù)語(yǔ)基本上全是從英文翻譯成中文的,事務(wù)的英文是transaction,英文直譯就是交易,買賣的意思,交易就是買的人付錢,賣的人交貨,不能付了錢不交貨,交了貨不付錢把,所以交易本身就是一種不可分割的操作。不知道是哪位大神把transaction翻譯成了事務(wù)(我想估計(jì)是他們也想不出什么更好的詞兒,只能隨便找一個(gè)了),事務(wù)這個(gè)詞兒完全沒(méi)有交易、買賣的意思,所以大家理解起來(lái)也會(huì)比較困難,外國(guó)人理解transaction可能更好理解一點(diǎn)吧~
三、MySQL中事務(wù)的語(yǔ)法
我們說(shuō)事務(wù)的本質(zhì)其實(shí)只是一系列數(shù)據(jù)庫(kù)操作,只不過(guò)這些數(shù)據(jù)庫(kù)操作符合ACID
特性而已,那么MySQL中如何將某些操作放到一個(gè)事務(wù)里去執(zhí)行的呢?我們下邊就來(lái)重點(diǎn)嘮叨嘮叨。
3.1 開(kāi)啟事務(wù)
我們可以使用下邊兩種語(yǔ)句之一來(lái)開(kāi)啟一個(gè)事務(wù):
語(yǔ)句一: BEGIN [WORK];
BEGIN
語(yǔ)句代表開(kāi)啟一個(gè)事務(wù),后邊的單詞WORK
可有可無(wú)。開(kāi)啟事務(wù)后,就可以繼續(xù)寫(xiě)若干條語(yǔ)句,這些語(yǔ)句都屬于剛剛開(kāi)啟的這個(gè)事務(wù)。
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> 加入事務(wù)的語(yǔ)句...
語(yǔ)句二: START TRANSACTION;
START TRANSACTION
語(yǔ)句和BEGIN
語(yǔ)句有著相同的功效,都標(biāo)志著開(kāi)啟一個(gè)事務(wù),比如這樣:
mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) mysql> 加入事務(wù)的語(yǔ)句...
不過(guò)比BEGIN
語(yǔ)句牛逼一點(diǎn)兒的是,可以在START TRANSACTION
語(yǔ)句后邊跟隨幾個(gè)修飾符,就是它們幾個(gè):
READ ONLY
:標(biāo)識(shí)當(dāng)前事務(wù)是一個(gè)只讀事務(wù),也就是屬于該事務(wù)的數(shù)據(jù)庫(kù)操作只能讀取數(shù)據(jù),而不能修改數(shù)據(jù)。
小提示:
其實(shí)只讀事務(wù)中只是不允許修改那些其他事務(wù)也能訪問(wèn)到的表中的數(shù)據(jù),對(duì)于臨時(shí)表來(lái)說(shuō)(我們使用CREATE TMEPORARY
TABLE創(chuàng)建的表),由于它們只能在當(dāng)前會(huì)話中可見(jiàn),所以只讀事務(wù)其實(shí)也是可以對(duì)臨時(shí)表進(jìn)行增、刪、改操作的。
READ WRITE
:標(biāo)識(shí)當(dāng)前事務(wù)是一個(gè)讀寫(xiě)事務(wù),也就是屬于該事務(wù)的數(shù)據(jù)庫(kù)操作既可以讀取數(shù)據(jù),也可以修改數(shù)據(jù)。WITH CONSISTENT SNAPSHOT
:?jiǎn)?dòng)一致性讀(先不用關(guān)心啥是個(gè)一致性讀,后邊的章節(jié)才會(huì)嘮叨)。
比如我們想開(kāi)啟一個(gè)只讀事務(wù)的話,直接把READ ONLY
這個(gè)修飾符加在START TRANSACTION
語(yǔ)句后邊就好,比如這樣:
START TRANSACTION READ ONLY;
如果我們想在START TRANSACTION
后邊跟隨多個(gè)修飾符
的話,可以使用逗號(hào)將修飾符
分開(kāi),比如開(kāi)啟一個(gè)只讀事務(wù)和一致性讀,就可以這樣寫(xiě):
START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;
或者開(kāi)啟一個(gè)讀寫(xiě)事務(wù)和一致性讀,就可以這樣寫(xiě):
START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT
不過(guò)這里需要大家注意的一點(diǎn)是,READ ONLY
和READ WRITE
是用來(lái)設(shè)置所謂的事務(wù)訪問(wèn)模式的,就是以只讀還是讀寫(xiě)的方式來(lái)訪問(wèn)數(shù)據(jù)庫(kù)中的數(shù)據(jù),一個(gè)事務(wù)的訪問(wèn)模式不能同時(shí)既設(shè)置為只讀的也設(shè)置為讀寫(xiě)的,所以我們不能同時(shí)把READ ONLY
和READ WRITE
放到START TRANSACTION
語(yǔ)句后邊。另外,如果我們不顯式指定事務(wù)的訪問(wèn)模式,那么該事務(wù)的訪問(wèn)模式就是讀寫(xiě)模式。
3.2 提交事務(wù)
開(kāi)啟事務(wù)之后就可以繼續(xù)寫(xiě)需要放到該事務(wù)中的語(yǔ)句了,當(dāng)最后一條語(yǔ)句寫(xiě)完了之后,我們就可以提交該事務(wù)了,提交的語(yǔ)句也很簡(jiǎn)單:COMMIT [WORK]
COMMIT
語(yǔ)句就代表提交一個(gè)事務(wù),后邊的WORK
可有可無(wú)。比如我們上邊說(shuō)張三給李四轉(zhuǎn)10元錢其實(shí)對(duì)應(yīng)MySQL中的兩條語(yǔ)句,我們就可以把這兩條語(yǔ)句放到一個(gè)事務(wù)中,完整的過(guò)程就是這樣:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE account SET balance = balance - 10 WHERE id = 1; Query OK, 1 row affected (0.02 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> UPDATE account SET balance = balance + 10 WHERE id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> COMMIT; Query OK, 0 rows affected (0.00 sec)
3.3 手動(dòng)中止事務(wù)
如果我們寫(xiě)了幾條語(yǔ)句之后發(fā)現(xiàn)上邊的某條語(yǔ)句寫(xiě)錯(cuò)了,我們可以手動(dòng)的使用下邊這個(gè)語(yǔ)句來(lái)將數(shù)據(jù)庫(kù)恢復(fù)到事務(wù)執(zhí)行之前的樣子:ROLLBACK [WORK]
ROLLBACK
語(yǔ)句就代表中止并回滾一個(gè)事務(wù),后邊的WORK
可有可無(wú)類似的。比如我們?cè)趯?xiě)張三給李四轉(zhuǎn)賬10元錢對(duì)應(yīng)的MySQL語(yǔ)句時(shí),先給張三扣了10元,然后一時(shí)大意只給李四賬戶上增加了1元,此時(shí)就可以使用ROLLBACK
語(yǔ)句進(jìn)行回滾,完整的過(guò)程就是這樣:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE account SET balance = balance - 10 WHERE id = 1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> UPDATE account SET balance = balance + 1 WHERE id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> ROLLBACK; Query OK, 0 rows affected (0.00 sec)
這里需要強(qiáng)調(diào)一下,ROLLBACK
語(yǔ)句是我們程序員手動(dòng)的去回滾事務(wù)時(shí)才去使用的,如果事務(wù)在執(zhí)行過(guò)程中遇到了某些錯(cuò)誤而無(wú)法繼續(xù)執(zhí)行的話,事務(wù)當(dāng)身會(huì)當(dāng)動(dòng)的回滾。
小提示:
我們這里所說(shuō)的開(kāi)啟、提交、中止事務(wù)的語(yǔ)法只是針對(duì)使用?框框時(shí)通過(guò)mysql客戶端程序與服務(wù)器進(jìn)行交互時(shí)控制事務(wù)的語(yǔ)法,如果大家使用的是別的客戶端程序,比如JDBC之類的,那需要參考相應(yīng)的文檔來(lái)看看如何控制事務(wù)。
3.4 支持事務(wù)的存儲(chǔ)引擎
MySQL中并不是所有存儲(chǔ)引擎都支持事務(wù)的功能,?前只有InnoDB
和NDB
存儲(chǔ)引擎支持(,如果某個(gè)事務(wù)中包含了修改使用不支持事務(wù)的存儲(chǔ)引擎的表,那么對(duì)該使用不支持事務(wù)的存儲(chǔ)引擎的表所做的修改將無(wú)法進(jìn)行回滾。比方說(shuō)我們有兩個(gè)表,tbl1
使用支持事務(wù)的存儲(chǔ)引擎InnoDB
,tbl2
使用不支持事務(wù)的存儲(chǔ)引擎MyISAM
,它們的建表語(yǔ)句如下所示:
mysql> CREATE TABLE demo14 ( i int ) engine=InnoDB; Query OK, 0 rows affected (0.05 sec) mysql> CREATE TABLE demo15 ( i int ) ENGINE=MyISAM; Query OK, 0 rows affected (0.01 sec)
我們看看先開(kāi)啟一個(gè)事務(wù),寫(xiě)一條插入語(yǔ)句后再回滾該事務(wù),demo14
和demo15
的表現(xiàn)有什么不同:
mysql> SELECT * FROM demo14; Empty set (0.00 sec) mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO demo14 VALUES(1); Query OK, 1 row affected (0.00 sec) mysql> ROLLBACK; Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM demo14; Empty set (0.00 sec)
以看到,對(duì)于使用支持事務(wù)的存儲(chǔ)引擎的demo14
表來(lái)說(shuō),我們?cè)诓迦胍粭l記錄再回滾后,demo14
就恢復(fù)到?jīng)]有插入記錄時(shí)的狀態(tài)了。再看看demo15
表的表現(xiàn):
mysql> SELECT * FROM demo14; Empty set (0.01 sec) mysql> SELECT * FROM demo15; Empty set (0.00 sec) mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO demo15 VALUES(1); Query OK, 1 row affected (0.00 sec) mysql> ROLLBACK; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> SELECT * FROM demo15; +------+ | i | +------+ | 1 | +------+ 1 row in set (0.01 sec)
可以看到,雖然我們使用了ROLLBACK
語(yǔ)句來(lái)回滾事務(wù),但是插入的那條記錄還是留在了demo15
表中。
3.5 自動(dòng)提交
MySQL中有一個(gè)系統(tǒng)變量autocommit
:
mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> set persist autocommit = 'ON'; Query OK, 0 rows affected (0.00 sec)
可以看到它的默認(rèn)值為ON
,也就是說(shuō)默認(rèn)情況下,如果我們不顯式的使用START TRANSACTION
或者BEGIN
語(yǔ)句開(kāi)啟一個(gè)事務(wù),那么每一條語(yǔ)句都算是一個(gè)獨(dú)立的事務(wù),這種特性稱之為事務(wù)的當(dāng)動(dòng)提交。假如我們?cè)趶埲蚶钏霓D(zhuǎn)賬10元時(shí)不以START TRANSACTION
或者BEGIN
語(yǔ)句顯式的開(kāi)啟一個(gè)事務(wù),那么下邊這兩條語(yǔ)句就相當(dāng)于放到兩個(gè)獨(dú)立的事務(wù)中去執(zhí)行:
UPDATE account SET balance = balance - 10 WHERE id = 1; UPDATE account SET balance = balance + 10 WHERE id = 2;
當(dāng)然,如果我們想關(guān)閉這種當(dāng)動(dòng)提交的功能,可以使用下邊兩種方法之一:
- 顯式的的使用
START TRANSACTION
或者BEGIN
語(yǔ)句開(kāi)啟一個(gè)事務(wù)。這樣在本次事務(wù)提交或者回滾前會(huì)暫時(shí)關(guān)閉掉當(dāng)動(dòng)提交的功能。 - 把系統(tǒng)變量
autocommit
的值設(shè)置為OFF
,就像這樣:SET autocommit = OFF;
這樣的話,我們寫(xiě)入的多條語(yǔ)句就算是屬于同一個(gè)事務(wù)了,直到我們顯式的寫(xiě)出COMMIT
語(yǔ)句來(lái)把這個(gè)事務(wù)提交掉,或者顯式的寫(xiě)出ROLLBACK
語(yǔ)句來(lái)把這個(gè)事務(wù)回滾掉。
3.6 隱式提交
當(dāng)我們使用START TRANSACTION
或者BEGIN
語(yǔ)句開(kāi)啟了一個(gè)事務(wù),或者把系統(tǒng)變量autocommit
的值設(shè)置為OFF
時(shí),事務(wù)就不會(huì)進(jìn)行當(dāng)動(dòng)提交,但是如果我們輸入了某些語(yǔ)句之后就會(huì)悄悄的提交掉,就像我們輸入了COMMIT
語(yǔ)句了一樣,這種因?yàn)槟承┨厥獾恼Z(yǔ)句而導(dǎo)致事務(wù)提交的情況稱為隱式提交,這些會(huì)導(dǎo)致事務(wù)隱式提交的語(yǔ)句包括:
- 定義或修改數(shù)據(jù)庫(kù)對(duì)象的數(shù)據(jù)定義語(yǔ)言(Data definition language,縮寫(xiě)為:
DDL
)。所謂的數(shù)據(jù)庫(kù)對(duì)象,指的就是數(shù)據(jù)庫(kù)、表、視圖、存儲(chǔ)過(guò)程等等這些東西。當(dāng)我們使用CREATE
、ALTER
、DROP
等語(yǔ)句去修改這些所謂的數(shù)據(jù)庫(kù)對(duì)象時(shí),就會(huì)隱式的提交前邊語(yǔ)句所屬于的事務(wù),就像這樣:
BEGIN;
SELECT ...
#事務(wù)中的一條語(yǔ)句UPDATE ...
#事務(wù)中的一條語(yǔ)句...
#事務(wù)中的其它語(yǔ)句CREATE TABLE ...
# `此語(yǔ)句會(huì)隱式的提交前邊語(yǔ)句所屬于的事務(wù)
- 隱式使用或修改
mysql
數(shù)據(jù)庫(kù)中的表
當(dāng)我們使用ALTER USER
、CREATE USER
、DROP USER
、GRANT
、RENAME USER
、REVOKE
、SET PASSWORD
等語(yǔ)句時(shí)也會(huì)隱式的提交前邊語(yǔ)句所屬于的事務(wù)。
- 事務(wù)控制或關(guān)于鎖定的語(yǔ)句
當(dāng)我們?cè)谝粋€(gè)事務(wù)還沒(méi)提交或者回滾時(shí)就又使用START TRANSACTION
或者BEGIN
語(yǔ)句開(kāi)啟了另一個(gè)事務(wù)時(shí),會(huì)隱式的提交上一個(gè)事務(wù),比如這樣:
BEGIN;
SELECT ...
#事務(wù)中的一條語(yǔ)句UPDATE ...
#事務(wù)中的一條語(yǔ)句...
# 事務(wù)中的其它語(yǔ)句BEGIN;
# 此語(yǔ)句會(huì)隱式的提交前邊語(yǔ)句所屬于的事務(wù)
或者當(dāng)前的autocommit
系統(tǒng)變量的值為OFF
,我們手動(dòng)把它調(diào)為ON
時(shí),也會(huì)隱式的提交前邊語(yǔ)句所屬的事務(wù)。
或者使用`LOCK TABLES、UNLOCK TABLES等關(guān)于鎖定的語(yǔ)句也會(huì)隱式的提交前邊語(yǔ)句所屬的事務(wù)。
- 加載數(shù)據(jù)的語(yǔ)句:比如我們使用
LOAD DATA
語(yǔ)句來(lái)批量往數(shù)據(jù)庫(kù)中導(dǎo)入數(shù)據(jù)時(shí),也會(huì)隱式的提交前邊語(yǔ)句所屬的事務(wù)。 - 關(guān)于MySQL復(fù)制的一些語(yǔ)句
使用START SLAVE
、STOP SLAVE
、RESET SLAVE
、CHANGE MASTER TO
等語(yǔ)句時(shí)也會(huì)隱式的提交前邊語(yǔ)句所屬的事務(wù)。 - 其它的一些語(yǔ)句
使用ANALYZE TABLE
、CACHE INDEX
、CHECK TABLE
、FLUSH
、 LOAD INDEX INTO CACHE
、OPTIMIZE TABLE
、REPAIR TABLE
、RESET
等語(yǔ)句也會(huì)隱式的提交前邊語(yǔ)句所屬的事務(wù)。
小提示:
上邊提到的一些語(yǔ)句,如果你都認(rèn)識(shí)并且知道是干嘛用的那再好不過(guò)了,不認(rèn)識(shí)也不要?餒,這里寫(xiě)出來(lái)只是為了內(nèi)容的完整性,把可能會(huì)導(dǎo)致事務(wù)隱式提交的情況都列舉一下,具體每個(gè)語(yǔ)句都是干嘛用的等我們遇到了再說(shuō)哈。
3.7 保存點(diǎn)
如果你開(kāi)啟了一個(gè)事務(wù),并且已經(jīng)敲了很多語(yǔ)句,忽然發(fā)現(xiàn)上一條語(yǔ)句有點(diǎn)問(wèn)題,你只好使用ROLLBACK
語(yǔ)句來(lái)讓數(shù)據(jù)庫(kù)狀態(tài)恢復(fù)到事務(wù)執(zhí)行之前的樣子,然后一切從頭再來(lái),總有一種一夜回到解放前的感覺(jué)。所以數(shù)據(jù)庫(kù)的提出了一個(gè)保存點(diǎn)(英文:savepoint
)的概念,就是在事務(wù)對(duì)應(yīng)的數(shù)據(jù)庫(kù)語(yǔ)句中打幾個(gè)點(diǎn),我們?cè)谡{(diào)用ROLLBACK
語(yǔ)句時(shí)可以指定會(huì)滾到哪個(gè)點(diǎn),而不是回到最初的原點(diǎn)。定義保存點(diǎn)的語(yǔ)法如下:SAVEPOINT 保存點(diǎn)名稱
當(dāng)我們想回滾到某個(gè)保存點(diǎn)時(shí),可以使用下邊這個(gè)語(yǔ)句(下邊語(yǔ)句中的單詞WORK
和SAVEPOINT
是可有可無(wú)的):ROLLBACK [WORK] TO [SAVEPOINT] 保存點(diǎn)名稱
不過(guò)如果ROLLBACK
語(yǔ)句后邊不跟隨保存點(diǎn)名稱的話,會(huì)直接回滾到事務(wù)執(zhí)行之前的狀態(tài)。
如果我們想刪除某個(gè)保存點(diǎn),可以使用這個(gè)語(yǔ)句:RELEASE SAVEPOINT 保存點(diǎn)名稱
下邊還是以張三向李四轉(zhuǎn)賬10元的例子展示一下保存點(diǎn)的用法,在執(zhí)行完扣除張三賬戶的錢10元
的語(yǔ)句之后打一個(gè)保存點(diǎn):
mysql> SELECT * FROM account; +----+--------+---------+ | id | name | balance | +----+--------+---------+ | 1 | 張三 | 12 | | 2 | 李四 | 2 | +----+--------+---------+ 2 rows in set (0.00 sec) mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE account SET balance = balance - 10 WHERE id = 1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SAVEPOINT s1; # 一個(gè)保存點(diǎn) Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM account; +----+--------+---------+ | id | name | balance | +----+--------+---------+ | 1 | 張三 | 2 | | 2 | 李四 | 2 | +----+--------+---------+ 2 rows in set (0.00 sec) mysql> UPDATE account SET balance = balance + 1 WHERE id = 2; # 更新錯(cuò)了 Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> ROLLBACK TO s1; # 回滾到保存點(diǎn)s1處 Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM account; +----+--------+---------+ | id | name | balance | +----+--------+---------+ | 1 | 張三 | 2 | | 2 | 李四 | 2 | +----+--------+---------+ 2 rows in set (0.01 sec)
總結(jié)
到此這篇關(guān)于MySQL之事務(wù)的簡(jiǎn)介的文章就介紹到這了,更多相關(guān)MySQL事務(wù)簡(jiǎn)介內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql5.6.19下子查詢?yōu)槭裁礋o(wú)法使用索引
這篇文章主要介紹了mysql5.6.19下子查詢?yōu)槭裁礋o(wú)法使用索引,需要的朋友可以參考下2014-08-08淺析MySQL如何實(shí)現(xiàn)事務(wù)隔離
使用過(guò)關(guān)系型數(shù)據(jù)庫(kù)的,應(yīng)該都事務(wù)的概念有所了解,知道事務(wù)有 ACID 四個(gè)基本屬性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability),今天我們主要來(lái)理解一下事務(wù)的隔離性2021-06-06MySQL刪除數(shù)據(jù)后自增主鍵ID不連貫問(wèn)題及解決
這篇文章主要介紹了MySQL刪除數(shù)據(jù)后自增主鍵ID不連貫問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09mysql時(shí)間戳格式化函數(shù)from_unixtime使用的簡(jiǎn)單說(shuō)明
mysql中的FROM_UNIXTIME函數(shù)可以數(shù)據(jù)庫(kù)中整型類的時(shí)間戳格式化為字符串的日期時(shí)間格式,下面這篇文章主要給大家介紹了關(guān)于mysql時(shí)間戳格式化函數(shù)from_unixtime使用的簡(jiǎn)單說(shuō)明,需要的朋友可以參考下2022-08-08mysql函數(shù)日期和時(shí)間函數(shù)匯總
這篇文章主要介紹了mysql函數(shù)日期和時(shí)間函數(shù)匯總,日期和時(shí)間函數(shù)主要用來(lái)處理日期和時(shí)間值,一般的日期函數(shù)除了使用??date???類型的參數(shù)外,也可以使用??datetime???或者??timestamp??類型的參數(shù),但會(huì)忽略這些值的時(shí)間部分2022-07-07CentOS7環(huán)境下安裝MySQL5.5數(shù)據(jù)庫(kù)
大家好,本篇文章主要講的是CentOS7環(huán)境下安裝MySQL5.5數(shù)據(jù)庫(kù),感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12