事務(wù)的基礎(chǔ)知識(shí)
1. 數(shù)據(jù)庫(kù)事務(wù)概述
事務(wù)是數(shù)據(jù)庫(kù)區(qū)別于文件系統(tǒng)的重要特性之一,當(dāng)我們有了事務(wù)就會(huì)讓數(shù)據(jù)庫(kù)中的數(shù)據(jù)始終保持 一致性,同時(shí)我們還能通過(guò)事務(wù)的機(jī)制 恢復(fù)到某個(gè)時(shí)間地點(diǎn)的數(shù)據(jù),這樣可以保證已提交到數(shù)據(jù)庫(kù)的修改不會(huì)因?yàn)橄到y(tǒng)崩潰而丟失。
1.1 存儲(chǔ)引擎的支持情況
查詢當(dāng)前 MySQL 支持的存儲(chǔ)引擎
show engines;
Engine | Support | Comment | Transactions | XA | Savepoints |
---|---|---|---|---|---|
MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
CSV | YES | CSV storage engine | NO | NO | NO |
FEDERATED | NO | Federated MySQL storage engine | |||
PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
MyISAM | YES | MyISAM storage engine | NO | NO | NO |
InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys(支持事務(wù)、行級(jí)鎖定和外鍵) | YES | YES | YES |
BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
ARCHIVE | YES | Archive storage engine | NO | NO | NO |
只有 InnoDB 存儲(chǔ)引擎是支持事務(wù)的。
1.2 基本概念
事務(wù):一組邏輯操作單元,使數(shù)據(jù)從一種狀態(tài)變換到另一種狀態(tài)。
事務(wù)處理的原則:保證所有事務(wù)都作為 一個(gè)工作單元 來(lái)執(zhí)行,即使出現(xiàn)了故障,都不能改變這種執(zhí)行方式。當(dāng)在一個(gè)事務(wù)中執(zhí)行多個(gè)操作時(shí),要么所有的事務(wù)都被提交(commit),那么這些修改就 永遠(yuǎn) 地保持下來(lái);要么 放棄 所做的所有 修改,整個(gè)事務(wù)回滾(rollback)到最初狀態(tài)。
例如:賬戶轉(zhuǎn)賬(aa 向 bb 轉(zhuǎn)賬 100 元)
# aa 減 100
update account set money = money - 100 where name = 'AA';
# bb 加 100
update account set money = money + 100 where name = 'BB';
以上的操作就是 "一組邏輯操作單元" ,在邏輯上(業(yè)務(wù)中)是不可分割的。
注意:如果在為 bb 加錢時(shí),出現(xiàn)故障或者錯(cuò)誤,那么將放棄 aa 減錢的操作,將錢退回給 aa。
1.3 事務(wù)的 ACIC 特性
1.3.1 原子性(atomicity)
原子性是指事務(wù)是 一個(gè)不可分割的工作單位,要么全部提交,要么全部失敗回滾。
即要么轉(zhuǎn)賬成功,要么轉(zhuǎn)賬失敗,是不存在中間的狀態(tài)。如果無(wú)法保證原子性會(huì)怎么樣?就會(huì)出現(xiàn)數(shù)據(jù)不一致的情形,a賬戶減去100元,而b賬戶增加100元操作失敗,系統(tǒng)將無(wú)故丟失100元。
1.3.2 一致性(consistency)
(建議參考 Wikipedia 對(duì) 一致性(consistency)的闡述)
根據(jù)定義,一致性是事務(wù)執(zhí)行前后,數(shù)據(jù)從一個(gè) 合法性狀態(tài) 變換另外一個(gè) 合法性狀態(tài) 。這種狀態(tài)是 語(yǔ)義上 的而不是語(yǔ)法上的,根據(jù)具體的業(yè)務(wù)有關(guān)。
那什么是合法的數(shù)據(jù)狀態(tài)呢?
滿足 預(yù)定的約束 的狀態(tài)就叫做合法的狀態(tài)。通俗一點(diǎn),這狀態(tài)是由我們自己來(lái)定義的(比如滿足現(xiàn)實(shí)世界中的約束)滿足這個(gè)狀態(tài),數(shù)據(jù)就是一致性的,不滿足這個(gè)狀態(tài),數(shù)據(jù)就是不一致的!,如果事務(wù)中的某個(gè)操作失敗了,系統(tǒng)就會(huì)自動(dòng)撤銷當(dāng)前正在執(zhí)行的事務(wù),返回到事務(wù)操作之前的狀態(tài)。
舉例1:賬戶的余額必須 >=0
a賬戶有200元,轉(zhuǎn)賬300元出去,此時(shí)賬戶余額為-100元。此時(shí)數(shù)據(jù)就是不一致的。因?yàn)槎x了余額必須 >=0 狀態(tài)(規(guī)則)。
舉例2:不管怎么操作,兩個(gè)賬戶的總余額必須不變
a賬戶200元,轉(zhuǎn)賬50元給b賬戶,a賬戶的錢扣了,但是b賬戶因?yàn)楦鞣N意外,余額并沒(méi)有增加,此時(shí)數(shù)據(jù)就是不一致的、因?yàn)槎x了a和b的總余額必須不變的狀態(tài)(規(guī)則)
舉例3:唯一性約束
在數(shù)據(jù)表中我們將 姓名 字段設(shè)置為 唯一性約束,這時(shí)當(dāng)事務(wù)進(jìn)行提交或者事務(wù)發(fā)生回滾的時(shí)候,如果數(shù)據(jù)表中的姓名不唯一,就破壞了事務(wù)的一致性要求。
1.3.3 隔離性(isolation)
事務(wù)的隔離性是指 一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾,即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù) 對(duì)并發(fā)的其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能相互干擾。
如果無(wú)法保證隔離性會(huì)怎么樣?
假設(shè)a賬戶有200元,b賬戶0元。a賬戶往b賬戶轉(zhuǎn)賬兩次,每次金額為50元,分別在兩個(gè)事務(wù)中執(zhí)行。如果無(wú)法保證隔離性,就可能會(huì)出現(xiàn) 數(shù)據(jù)不一致 的情形:
update accounts set money = money - 50 where name = 'aa';
update accounts set money = money + 50 where name = 'bb';
該行為就造成了 "臟寫"。
1.3.4 持久性(durability)
持久性是指一個(gè)事務(wù)一旦被提交,它對(duì)數(shù)據(jù)庫(kù)中的數(shù)據(jù)的改變就是 永久性的(寫入磁盤了),接下來(lái)的其他操作和數(shù)據(jù)庫(kù)故障不應(yīng)該對(duì)其有任何影響。
持久性是通過(guò) 事務(wù)日志 來(lái)保證的。日志包括了 重做日志 和 回滾日志。
當(dāng)我們通過(guò)事務(wù)對(duì)數(shù)據(jù)進(jìn)行修改的時(shí)候,首先會(huì)將數(shù)據(jù)庫(kù)的變化信息記錄到 重做日志 中,然后再對(duì)數(shù)據(jù)庫(kù)中對(duì)應(yīng)的行進(jìn)行修改。這樣做的好處是,即使數(shù)據(jù)庫(kù)系統(tǒng)崩潰,數(shù)據(jù)庫(kù)重啟后也能找到?jīng)]有更新到數(shù)據(jù)庫(kù)系統(tǒng)中的 重做日志,重新執(zhí)行,從而使事務(wù)具有持久性。
1.3.5 總結(jié) - 重要概念
ACID 是事務(wù)的四大特性,在這四個(gè)特性中 原子性是 "基礎(chǔ)",隔離性是 "手段",一致性是 "約束條件",而持久性是 "目的"。
數(shù)據(jù)庫(kù)事務(wù),其實(shí)就是數(shù)據(jù)庫(kù)設(shè)計(jì)者為了方便起見(jiàn),把需要保證 原子性,隔離性,一致性 和 持久性? 的一個(gè)或多個(gè)數(shù)據(jù)庫(kù)操作?稱為一個(gè)事務(wù)。
1.4 事務(wù)的狀態(tài)
我們現(xiàn)在知道 事務(wù) 是一個(gè)抽象的概念,它其實(shí)對(duì)應(yīng)著 一個(gè)或多個(gè)數(shù)據(jù)庫(kù)操作(dml),MySQL 根據(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)有刷新到磁盤 時(shí),我們就說(shuō)該事務(wù)處在 部分提交的 狀態(tài)。(在刷盤之前)
- 失敗的(failed)
當(dāng)事務(wù)處在 活動(dòng)的 或者 部分提交的 狀態(tài)時(shí),可能遇到了某些錯(cuò)誤(數(shù)據(jù)庫(kù)自身的錯(cuò)誤,操作系統(tǒng)錯(cuò)誤或者直接斷電等)而無(wú)法繼續(xù)執(zhí)行,或者人為的停止當(dāng)前事務(wù)的執(zhí)行,那個(gè)該事務(wù)處在 失敗的 狀態(tài)。
- 中止的(aborted)
如果事務(wù)執(zhí)行了一部分而變?yōu)?失敗的 狀態(tài),那么就需要把已經(jīng)修改的事務(wù)中的操作還原到事務(wù)執(zhí)行前的狀態(tài)(回滾)。這時(shí)該事務(wù)處在 中止的 狀態(tài)。
- 提交的(committed)
當(dāng)一個(gè)處在 部分提交的 狀態(tài)的事務(wù)將修改過(guò)的數(shù)據(jù)都 同步到磁盤 上之后,我們就可以說(shuō)該事務(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)。
2.如何使用事務(wù)
一個(gè)事務(wù)的完整過(guò)程:
步驟1:開(kāi)啟事務(wù)
步驟2:一系列的 dml 操作 ...
步驟3:事務(wù)結(jié)束的狀態(tài)(提交 commit,中止 rollback)
使用事務(wù)有兩種方式,分別為 顯示事務(wù) 和 隱式事務(wù)。
2.1 顯式事務(wù)
start transaction 或者 begin,作用是 顯式開(kāi)啟一個(gè)事務(wù)。
步驟1:開(kāi)啟事務(wù)
BEGIN;
# 或者
START TRANSACTION;
注意:使用以上的方式開(kāi)啟事務(wù),是不受 autocommit (自動(dòng)提交)變量影響的。
start transaction 語(yǔ)句相較于 begin?特別之處在于,后面能跟隨幾個(gè) 修飾符:
- read only:只讀事務(wù)。屬于該事務(wù)的數(shù)據(jù)庫(kù)操作 只能讀取數(shù)據(jù),不能修改數(shù)據(jù)。
補(bǔ)充:只讀事務(wù)中只是不允許修改哪些其他事務(wù)也能訪問(wèn)到表中的數(shù)據(jù)(事務(wù)共享表 - 數(shù)據(jù)),對(duì)于臨時(shí)表來(lái)說(shuō)(使用 create tmeporary table 創(chuàng)建的表),由于它們只能在當(dāng)前會(huì)話中可見(jiàn)(事務(wù)獨(dú)享表 - 數(shù)據(jù)),所以只讀事務(wù)其實(shí)也是可以對(duì)臨時(shí)表進(jìn)行增,刪,改操作的。
- read write:可讀寫事務(wù)(默認(rèn))。屬于該事務(wù)的數(shù)據(jù)庫(kù)操作 可以讀取數(shù)據(jù),也可以修改數(shù)據(jù)。
- with consistent snapshot:開(kāi)啟一致性讀。
比如:
START TRANSACTION READ only; # 開(kāi)啟一個(gè)只讀事務(wù) START TRANSACTION READ only, WITH CONSISTENT SNAPSHOT; # 開(kāi)啟只讀事務(wù)和一致性讀 START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT; # 開(kāi)啟讀寫事務(wù)和一致性讀
步驟2:一系列事務(wù)中的操作(主要是dml操作,不含ddl)
步驟3:事務(wù)結(jié)束的狀態(tài)(提交 commit,中止 rollback)
# 提交事務(wù),當(dāng)提交事務(wù)后,對(duì)數(shù)據(jù)庫(kù)的修改時(shí)永久性的
commit;
# 回滾事務(wù),即撤銷正在進(jìn)行的所有沒(méi)有提交的修改
rollback;
# 將事務(wù)回滾到某個(gè)保存點(diǎn)
rollback to [savepoint];
其中關(guān)于 savepoint 相關(guān)操作有:
- 創(chuàng)建保存點(diǎn):
# 在事務(wù)中創(chuàng)建保存點(diǎn),方便后續(xù)針對(duì)保存點(diǎn)進(jìn)行回滾,一個(gè)事務(wù)中可以存在多個(gè)保存點(diǎn)
savepoint 保存點(diǎn)名稱;
- 刪除保存點(diǎn):
# 刪除某個(gè)保存點(diǎn)
release savepoint 保存點(diǎn)名稱;
2.2 隱式事務(wù)
MySQL 中有一個(gè)系統(tǒng)變量 autocommit(默認(rèn):開(kāi)啟):
- 查看 autocommit
SHOW VARIABLES LIKE 'autocommit';
# 或者
SELECT @@autocommit;
# 或者
SELECT @@global.autocommit;
- 設(shè)置 autocommit
SET autocommit = TRUE; # 會(huì)話級(jí)別,只在當(dāng)前會(huì)話生效
# 或者
SET GLOBAL autocommit = TRUE; # 注意:全局設(shè)置,MySQL服務(wù)器重啟后失效
默認(rèn)情況下,如果我們不顯式的使用 start transaction 或者 begin 語(yǔ)句開(kāi)啟一個(gè)事務(wù),那么每一條語(yǔ)句都算是一個(gè)獨(dú)立的事務(wù),這種特性稱之為事務(wù)的 自動(dòng)提交。也就是說(shuō),不以 start transaction 或者 begin 語(yǔ)句顯式的開(kāi)啟一個(gè)事務(wù),那么執(zhí)行的 dml 操作就相當(dāng)于放到獨(dú)立的事務(wù)中執(zhí)行。
關(guān)閉 自動(dòng)提交 的功能兩中方法:
- 顯式使用 start transaction 或者 begin 語(yǔ)句開(kāi)啟一個(gè)事務(wù)。這樣在本次事務(wù)提交或者回滾前會(huì)暫時(shí)關(guān)閉 自動(dòng)提交 的功能。
- 把系統(tǒng)變量 autocommit 的值設(shè)置為 OFF。
SET autocommit = false; # 會(huì)話級(jí)別,只在當(dāng)前會(huì)話生效
#或者
SET autocommit = OFF; # 會(huì)話級(jí)別,只在當(dāng)前會(huì)話生效
#或者
SET autocommit = 0; # 會(huì)話級(jí)別,只在當(dāng)前會(huì)話生效
這樣的話,寫入的多條語(yǔ)句就算是屬于同一個(gè)事務(wù)了,直到我們顯式的寫出 commit 或者 rollback?(提交或回滾)。
補(bǔ)充:Oracle 默認(rèn)不自動(dòng)提交事務(wù),需要手動(dòng) commit 或者 rollback,而 MySQL 默認(rèn)自動(dòng)提交。
2.3 隱式提交數(shù)據(jù)的情況(重要)
- 使用數(shù)據(jù)庫(kù)定義語(yǔ)言(DDL)
當(dāng)我們使用 create,alter,drop 等語(yǔ)句去 修改數(shù)據(jù)庫(kù)對(duì)象 時(shí)(數(shù)據(jù)庫(kù),表,視圖,觸發(fā)器,存儲(chǔ)過(guò)程,視圖),就會(huì)隱式的提交前面語(yǔ)句所屬于的事務(wù):
begin; # 開(kāi)啟事務(wù)
select .... # 事務(wù)中的語(yǔ)句
update .... # 事務(wù)中的語(yǔ)句
.... # 事務(wù)中的語(yǔ)句
create table .... # 此時(shí)會(huì)隱式的提交前邊語(yǔ)句所屬于的事務(wù)
- 隱式使用或者修改 "mysql" 庫(kù)中的表(這里的mysql指的系統(tǒng)數(shù)據(jù)庫(kù)中名字為 "mysql" 數(shù)據(jù)庫(kù))。
當(dāng)我們使用 alter user,create user,drop user,grant,rename user,revoke,set password 等語(yǔ)句 修改用戶,權(quán)限 時(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; # 開(kāi)啟事務(wù) select .... # 事務(wù)中的語(yǔ)句 update .... # 事務(wù)中的語(yǔ)句 .... # 事務(wù)中的語(yǔ)句 begin; # 又開(kāi)啟了一個(gè)事務(wù) 此時(shí)會(huì)隱式的提交上一個(gè)事務(wù)
- 當(dāng)前的 autocommit 系統(tǒng)變量的值為 OFF,我們手動(dòng)修改為 ON 時(shí),也會(huì) 隱式的提交 前面語(yǔ)句所屬的事務(wù)。
- 使用 lock tables,unlock tables 等關(guān)于 "鎖" 定的語(yǔ)句時(shí)也會(huì) 隱式的提交 前邊語(yǔ)句所屬的事務(wù)。
-
關(guān)于 MySQL 復(fù)制的一些語(yǔ)句(主從復(fù)制)
使用 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(優(yōu)化表),repair table,reset 等語(yǔ)句也會(huì) 隱式的提交 前邊語(yǔ)句所屬的事務(wù)。
2.4 舉例:提交與回滾 - 顯式與隱式
# 創(chuàng)建測(cè)試表
create TABLE IF NOT EXISTS xld_begin(
id INT UNSIGNED PRIMARY KEY auto_increment COMMENT '主鍵id',
name VARCHAR(15) NOT NULL COMMENT '名稱',
age TINYINT UNSIGNED COMMENT '年齡',
INDEX idx_age(age)
)ENGINE = INNODB DEFAULT CHARSET = utf8;
- 回滾(rollback) - 事務(wù)
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 查看自動(dòng)提交是否開(kāi)啟
SHOW VARIABLES LIKE '%autocommit%'; # ON 開(kāi)啟
# 給表添加一條記錄
INSERT INTO xld_begin(name,age)VALUE ('張三',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 開(kāi)啟一個(gè)事務(wù)
BEGIN;
# 在事務(wù)中添加一條記錄
INSERT INTO xld_begin(name,age) VALUE ('李四',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 程序錯(cuò)誤
INSERT INTO xld_begin(name,age)VALUE (NULL,10);
# 回滾事務(wù) - 事務(wù)結(jié)束
ROLLBACK;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
- 提交(commit) - 事務(wù)
# 清空表 (DDL)不受事務(wù)控制
TRUNCATE TABLE xld_begin;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 查看自動(dòng)提交是否開(kāi)啟
SHOW VARIABLES LIKE '%autocommit%'; # ON 開(kāi)啟
# 給表添加一條記錄
INSERT INTO xld_begin(name,age)VALUE ('張三',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 開(kāi)啟一個(gè)事務(wù)
BEGIN;
# 在事務(wù)中給表添加一條記錄
insert into xld_begin(name,age) VALUE ('李四',12);
# 修改張三的年齡
update xld_begin set age = 12 WHERE name = '張三';
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 提交事務(wù) - 事務(wù)結(jié)束
COMMIT;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
- 鏈?zhǔn)聞?wù)
# 清空表 (DDL)不受事務(wù)控制
TRUNCATE TABLE xld_begin;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 查看自動(dòng)提交是否開(kāi)啟
SHOW VARIABLES LIKE '%autocommit%'; # ON 開(kāi)啟
# 開(kāi)啟鏈?zhǔn)聞?wù)
SET @@SESSION.completion_type = 1;
# 查看鏈?zhǔn)聞?wù)是否開(kāi)啟成功
SELECT @@session.completion_type;
# 給表添加一條記錄
INSERT INTO xld_begin(name,age)VALUE ('張三',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 開(kāi)啟事務(wù)
BEGIN;
# 在事務(wù)中給表添加一條記錄
insert into xld_begin(name,age) VALUE ('李四',12);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 提交事務(wù) - 事務(wù)結(jié)束
COMMIT;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
# 在事務(wù)中給表添加一條記錄
insert into xld_begin(name,age) VALUE ('王五',14);
# 程序錯(cuò)誤
INSERT INTO xld_begin(name,age)VALUE (NULL,10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
ROLLBACK;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin;
completion_type 系統(tǒng)變量(全局/會(huì)話)的使用:
- 查看 completion_type
SHOW VARIABLES LIKE '%completion%';
# 或者
SELECT @@completion_type;
- 設(shè)置 completion_type
SET @@completion_type = 1; # 開(kāi)啟鏈?zhǔn)绞聞?wù)
-
completion_type 的參數(shù)配置說(shuō)明:
- completion_type = 0(默認(rèn)),當(dāng)我們執(zhí)行 commit 時(shí)會(huì)提交事務(wù),在執(zhí)行下一個(gè)事務(wù)時(shí),需要使用 start transaction 或者 begin 來(lái)開(kāi)啟。
- completion_type = 1,這種情況下,當(dāng)我們執(zhí)行 commit 提交事務(wù)后,相當(dāng)于執(zhí)行了 commit and chain,也就是開(kāi)啟一個(gè) 鏈?zhǔn)绞聞?wù),即當(dāng)我們提交事務(wù)之后會(huì)開(kāi)啟一個(gè)相同隔離級(jí)別的事務(wù)。(注意:此時(shí)的"自動(dòng)提交"是失效的,必須手動(dòng)結(jié)束事務(wù))
- completion_type = 2,這種情況下 commit = commit and release,當(dāng)我們執(zhí)行 commit 提交事務(wù)后,會(huì)自動(dòng)與服務(wù)器斷開(kāi)連接。
2.5 測(cè)試 innodb 與 myisam 事務(wù)的支持情況
# 創(chuàng)建 innodb 事務(wù)表
create TABLE IF NOT EXISTS xld_begin_innodb(
id int UNSIGNED PRIMARY KEY auto_increment COMMENT '主鍵id',
name VARCHAR(15) NOT NULL COMMENT '名稱',
age TINYINT UNSIGNED COMMENT '年齡',
INDEX idx_age(age)
) ENGINE = INNODB DEFAULT charset = utf8;
# 創(chuàng)建 myisam 事務(wù)表
create TABLE IF not EXISTS xld_begin_myisam(
id INT UNSIGNED PRIMARY KEY auto_increment COMMENT '主鍵id',
name VARCHAR(15) NOT NULL COMMENT '名稱',
age TINYINT UNSIGNED COMMENT '年齡',
index idx_age(age)
)ENGINE = myisam DEFAULT charset = utf8;
- Innodb 存儲(chǔ)引擎支持事務(wù)
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_innodb;
# 開(kāi)啟事務(wù)
BEGIN;
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_innodb(name,age) value ('張三',10);
# 程序錯(cuò)誤
INSERT INTO xld_begin_innodb(name,age) value (NULL,10);
# 回滾事務(wù) - 事務(wù)結(jié)束
ROLLBACK;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_innodb;
- Myisam 存儲(chǔ)引擎不支持事務(wù)
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_myisam;
# 開(kāi)啟事務(wù)
BEGIN;
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_myisam(name,age) value ('張三',12);
# 程序錯(cuò)誤
INSERT INTO xld_begin_myisam(name,age) value (NULL,12);
# 回滾事務(wù) - 事務(wù)結(jié)束
ROLLBACK;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_myisam;
2.6 舉例 :事務(wù)保存點(diǎn)(savepoint)
# 創(chuàng)建表
create TABLE IF not EXISTS xld_begin_savepoint(
id int UNSIGNED PRIMARY KEY auto_increment COMMENT '主鍵id',
name VARCHAR(15) not null COMMENT '名稱',
age TINYINT UNSIGNED COMMENT '年齡',
index idx_age(age)
)ENGINE = INNODB DEFAULT charset = utf8;
- 保存點(diǎn)的使用
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 開(kāi)啟事務(wù)
BEGIN;
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('張三',10);
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('李四',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 創(chuàng)建保存點(diǎn)
savepoint xld_savepoint1;
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('王五',10);
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('劉六',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 創(chuàng)建保存點(diǎn)
savepoint xld_savepoint2;
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('王八',10);
# 在事務(wù)中給表添加一條記錄
INSERT INTO xld_begin_savepoint(name,age) value ('林九',10);
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 回滾到保存點(diǎn) xld_savepoint2
ROLLBACK TO xld_savepoint2; # 注意:此時(shí),事務(wù)還未結(jié)束
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 回滾到保存點(diǎn) xld_savepoint1
ROLLBACK TO xld_savepoint1; # 注意:此時(shí),事務(wù)還未結(jié)束
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
# 提交事務(wù) - 事務(wù)結(jié)束
COMMIT;
# 查詢數(shù)據(jù)
SELECT * FROM xld_begin_savepoint;
3. 事務(wù)的隔離級(jí)別
MySQL 是一個(gè) 客戶端(C)/ 服務(wù)器(S) 架構(gòu)的軟件,對(duì)于同一個(gè)服務(wù)器來(lái)說(shuō),可以有若干個(gè)客戶端與之連接,每個(gè)客戶端與服務(wù)器連接上之后,就可以稱為一個(gè)會(huì)話(Session)。
每個(gè)客戶端可以在自己的會(huì)話中向服務(wù)器發(fā)出請(qǐng)求語(yǔ)句(DML),一個(gè)請(qǐng)求語(yǔ)句(DML)可能是某個(gè)事務(wù)的一部分,也就是說(shuō) MySQL 服務(wù)器可能會(huì)同時(shí)處理多個(gè)事務(wù)。事務(wù)是有 隔離 的特性的,理論上在某個(gè)事務(wù) 對(duì)某個(gè)數(shù)據(jù)進(jìn)行訪問(wèn) 時(shí),其他事務(wù)應(yīng)該進(jìn)行 排隊(duì),當(dāng)該事務(wù)提交之后,其他事務(wù)才可以繼續(xù)訪問(wèn)這個(gè)數(shù)據(jù)。但是這樣對(duì) 性能影響太大,我們既想保持事務(wù)的 隔離性,又想讓服務(wù)器在處理多個(gè)事務(wù)時(shí)(同一個(gè)數(shù)據(jù)) 性能盡量高些,那就看二者如何權(quán)衡取舍了。
3.1 數(shù)據(jù)準(zhǔn)備
# 自己想辦法吧?。?!
3.2 數(shù)據(jù)并發(fā)問(wèn)題
針對(duì)事務(wù)的隔離性和并發(fā)性,我們?cè)趺醋鋈∩崮??先看一下訪問(wèn)相同數(shù)據(jù)的事務(wù)在 不保證串行執(zhí)行(也就是執(zhí)行完一個(gè)再執(zhí)行另一個(gè))的情況下可能會(huì)出現(xiàn)哪些問(wèn)題:
1. 臟寫(Dirty Write)
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B。如果 事務(wù)A 修改了 另一個(gè) 事務(wù)B 修改過(guò)且未提交 的數(shù)據(jù),那就意味著發(fā)生了 臟寫,示意圖如下:
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B ,事務(wù)B 先將 id 為1的name更新為 '李四',然后 事務(wù)A 接著又把這條 id 為1的name更新為 ’張三‘ 且提交(commit)了。如果之后 事務(wù)B 進(jìn)行了回滾,那么 事務(wù)A 中的更新也將不復(fù)存在,這種現(xiàn)象就稱之為 臟寫。這時(shí) 事務(wù)A 就沒(méi)有效果了,明明把數(shù)據(jù)更新了,最后也提交事務(wù)了,最后看到的數(shù)據(jù)什么變化也沒(méi)有。
在 MySQL 默認(rèn)的事務(wù)隔離級(jí)別下,在 事務(wù)A 中執(zhí)行的更新語(yǔ)句會(huì)處于等待狀態(tài)(加鎖了)。
2. 臟讀(Dirty Read)
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B。事務(wù)A 讀取 了已經(jīng)被 事務(wù)B 更新但還沒(méi)有提交 的數(shù)據(jù)。之后若 事務(wù) B 回滾,事務(wù)A 讀取 的內(nèi)容就是 臨時(shí)且無(wú)效 的。(一個(gè)事務(wù)讀到了,另一個(gè)事務(wù)修改了但未提交的數(shù)據(jù))
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B ,事務(wù)B 先將 id 為1的name更新為 '張三',然后 事務(wù)A 再去查詢這條 id 為1的記錄,如果讀的name的值為'張三',而 事務(wù)B 不久之后進(jìn)行了回滾,那么 事務(wù)A 中就相當(dāng)于讀到了一個(gè)不存在的數(shù)據(jù),這種現(xiàn)象就稱之為 臟讀。
3. 不可重復(fù)讀(Non-Repeatable Read)
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B。事務(wù)A 讀取 了一條記錄,然后在 事務(wù)B 中 更新了且提交(commit)該記錄。隨之 事務(wù)A 再次讀取了該記錄,值就不同了。那這就意味著發(fā)生了 不可重復(fù)讀。(在同一個(gè)事務(wù)中,多次執(zhí)行相同的語(yǔ)句,但每次讀取的結(jié)果不同)
有兩個(gè)會(huì)話 sessionA,sessionB。session B 中提交了幾個(gè) 隱式事務(wù)(注意是隱式事務(wù),意味著語(yǔ)句結(jié)束事務(wù)就提交了),這些事務(wù)都修改了 id 為1的name值,每次事務(wù)提交之后,如果 Session A 中的事務(wù)都可以查看到最新的值,這種現(xiàn)象也被稱之為 不可重復(fù)讀。
4. 幻讀(Phantom)
有兩個(gè)事務(wù):事務(wù)A,事務(wù)B。事務(wù)A 從一個(gè)表中 讀取 了一條記錄,然后 事務(wù)B 給該表 插入 了一些新的記錄。之后,如果 事務(wù)A 再次讀取 同一個(gè)表,結(jié)果集出現(xiàn)了多幾行的話。那就意味著發(fā)生了 幻讀。()
有兩個(gè)會(huì)話 sessionA,sessionB。session A 中的事務(wù)先根據(jù)條件 id > 0 這個(gè)條件查詢數(shù)據(jù),得到了 name 為 '張三' 的記錄。之后 session B 中提交了一個(gè) 隱式事務(wù),該事務(wù)向表中插入了一條新紀(jì)錄。之后 session A 中的事務(wù)再次根據(jù)相同的條件 id > 0 查詢數(shù)據(jù),得到的結(jié)果集中包含了 session B 中的事務(wù)新插入的那條記錄,這種現(xiàn)象也就稱為 幻讀(我們把新插入的那些記錄稱之為 幻影記錄)。
注意1:
有的人會(huì)有疑問(wèn),那如果 session B 中 刪除了 一些符合 id > 0 的記錄而不是插入新紀(jì)錄,那 session A 之后再根據(jù) id > 0 的條件讀取的 記錄變少了,這種現(xiàn)象算不算 幻讀 呢?這種現(xiàn)象 不屬于幻讀,幻讀 強(qiáng)調(diào)的是一個(gè)事務(wù)按照某個(gè) 相同條件多次讀取 記錄時(shí),后讀取時(shí)讀到了之前 沒(méi)有讀到的記錄。
注意2:
那對(duì)于先前已經(jīng)讀到的記錄,之后又讀取不到這種情況,算啥呢?這相當(dāng)于對(duì)每一條記錄都發(fā)生了 不可重復(fù)讀 的現(xiàn)象。幻讀 只是 重點(diǎn)強(qiáng)調(diào)了現(xiàn)在讀取到了,之前讀取時(shí) ,沒(méi)有獲取的到記錄。
3.3 SQL 中的四種隔離級(jí)別
上面介紹了幾種并發(fā)事務(wù)執(zhí)行過(guò)程中可能遇到的一些問(wèn)題,這些問(wèn)題有輕重緩急之分,我們給這些問(wèn)題按照嚴(yán)重性排一下序:
臟寫 > 臟讀 > 不可重復(fù)讀 > 幻讀
如何通過(guò)舍棄一部分隔離性來(lái)?yè)Q取一部分性能呢?
答:設(shè)立一些隔離級(jí)別,隔離級(jí)別越低,并發(fā)問(wèn)題發(fā)生的就越多,性能就越好。
在 SQL 標(biāo)準(zhǔn) 中設(shè)立了4個(gè) 隔離級(jí)別:
- read uncommitted(讀未提交): 在該隔離級(jí)別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。不能避免:臟讀,不可重復(fù)讀,幻讀。
- read committed(讀已提交):該隔離級(jí)別滿足了隔離的簡(jiǎn)單定義:一個(gè)事務(wù)只能看見(jiàn)已經(jīng)提交事務(wù)所做的改變。這也是大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)的默認(rèn)隔離級(jí)別(但不是 MySQL 默認(rèn) 的)。可以避免臟讀。但不能避免:不可重復(fù)讀,幻讀。
- repeatable read(可重復(fù)讀):該隔離級(jí)別可以做到,事務(wù)A 在讀到一條數(shù)據(jù)之后,此時(shí) 事務(wù)B 對(duì)該數(shù)據(jù)進(jìn)行了修改并提交,那么 事務(wù)A 再讀該數(shù)據(jù),讀到的還是原來(lái)的內(nèi)容。可以避免臟讀,不可重復(fù)讀。但不能避免:幻讀
- serializable(可串行化):該隔離級(jí)別可以確保事務(wù)在多次讀取表中數(shù)據(jù)時(shí)都是相同的數(shù)據(jù)。在這事務(wù)持續(xù)期間,禁止其他事務(wù)對(duì)該表執(zhí)行插入,更新和刪除操作。所有的并發(fā)問(wèn)題都可以避免,但性能十分低下。
SQL 標(biāo)準(zhǔn) 中規(guī)定,針對(duì)不同的隔離級(jí)別,并發(fā)事務(wù)可以發(fā)生不同的問(wèn)題,具體情況如下:
注意:由于"臟寫"這個(gè)問(wèn)題太嚴(yán)重了,不論是那種隔離級(jí)別,都不允許"臟寫"的情況發(fā)生。
不同的隔離級(jí)別有不同的現(xiàn)象,并有不同的鎖和并發(fā)機(jī)制,隔離級(jí)別越高,數(shù)據(jù)庫(kù)的并發(fā)性能就越差,4種事務(wù)隔離級(jí)別與并發(fā)性能的關(guān)系如下:
3.4 MySQL 支持的四種隔離級(jí)別
不同的數(shù)據(jù)庫(kù)廠商對(duì) SQL 標(biāo)準(zhǔn)中規(guī)定的四種隔離級(jí)別的支持也是不一樣的。比如,Oracle 就只支持 read committed(讀已提交,默認(rèn)隔離級(jí)別) 和 serializable(串行化)。MySQL 雖然支持4種隔離級(jí)別,但與 SQL 標(biāo)準(zhǔn)中所規(guī)定的各級(jí)隔離級(jí)別允許發(fā)生的問(wèn)題卻有些出入,MySQL 在 repeatable read(可重復(fù)讀) 隔離級(jí)別下,是可以禁止 幻讀 問(wèn)題發(fā)生的。
MySQL 的默認(rèn)隔離級(jí)別為:repeatable read。
- 查看 MySQL 的默認(rèn)隔離級(jí)別:transaction_isolation(全局/會(huì)話)
# 5.7.20 版本之前使用:
SHOW VARIABLES LIKE '%tx_isolation%';
# 5.7.20 版本之后使用(transaction_isolation 替換了 tx_isolation):
SHOW VARIABLES LIKE '%transaction_isolation%';
# 或者
SELECT @@transaction_isolation; # 同時(shí)支持全局和會(huì)話
3.5 如何設(shè)置事務(wù)的隔離級(jí)別
通過(guò)下面的語(yǔ)句修改事務(wù)的隔離級(jí)別:
- 方式1:
set [global | session] transaction isolation level 隔離級(jí)別;
# 其中,隔離級(jí)別格式:
> read uncommitted
> read committed
> repeatable read
> serializable
例如:設(shè)置會(huì)話中的隔離級(jí)別為:讀已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
- 方式2:
set [global | session] transaction_isolation = '隔離級(jí)別';
# 其中,隔離級(jí)別格式:
> read-uncommitted
> read-committed
> repeatable-read
> serializable
例如:設(shè)置會(huì)話中的隔離級(jí)別為:讀已提交
SET SESSION TRANSACTION_ISOLATION = 'read-committed';
關(guān)于設(shè)置時(shí)使用 global 或 session 的影響:
- 使用 global 關(guān)鍵字(在全局范圍影響):
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
#或者
SET GLOBAL TRANSACTION_ISOLATION = 'read-committed';
注意:
- 當(dāng)前已經(jīng)存在的會(huì)話無(wú)效
- 只對(duì)執(zhí)行完該語(yǔ)句之后產(chǎn)生的會(huì)話起作用
- 使用 session 關(guān)鍵字(在會(huì)話范圍影響):
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
#或者
SET SESSION TRANSACTION_ISOLATION = 'read-committed';
注意:
- 對(duì)當(dāng)前會(huì)話的所有后續(xù)的事務(wù)有效
- 如果在事務(wù)與事務(wù)之間執(zhí)行,則對(duì)后續(xù)的事務(wù)有效
- 該語(yǔ)句可以在已經(jīng)開(kāi)啟事務(wù)中間執(zhí)行,但不會(huì)影響當(dāng)前正在執(zhí)行的事務(wù),只對(duì)后續(xù)的事務(wù)有效
如果在服務(wù)器啟動(dòng)時(shí)相改變事務(wù)的默認(rèn)隔離級(jí)別,可以修改啟動(dòng)參數(shù) transaction_isolation 的值。比如,在啟動(dòng)服務(wù)器指定了 transaction_isolation = read-committed。那么事務(wù)的默認(rèn)隔離級(jí)別就從原來(lái)的 repeatable-read(可重復(fù)讀) 變成了 read-committed(讀已提交)。
小結(jié):
數(shù)據(jù)庫(kù)規(guī)定了多種事務(wù)的隔離級(jí)別,不同隔離級(jí)別對(duì)應(yīng)不同的干擾程度,隔離級(jí)別越高,數(shù)據(jù)一致性就越好,但相對(duì)的并發(fā)性能就越差。
3.6 不同隔離級(jí)別舉例
- 初始化數(shù)據(jù):
# 創(chuàng)建表
create table if not exists xld_transaction_isolation(
id INT PRIMARY KEY auto_increment COMMENT '主鍵id',
name VARCHAR(15) not NULL COMMENT '名稱',
money int DEFAULT 0 COMMENT '金額'
)ENGINE = INNODB DEFAULT charset = utf8;
# 新增數(shù)據(jù)
INSERT into xld_transaction_isolation (name,money) VALUES('張三',100),('李四',80);
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 初始化表中數(shù)據(jù):
id name monry
1 張三 100
2 李四 80
1. 演示:臟讀 - 讀未提交(read-uncommitted):
- 事務(wù)A 先執(zhí)行:
# 設(shè)置事務(wù)隔離級(jí)別為:讀未提交
SET SESSION transaction_isolation = 'read-uncommitted';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
# 開(kāi)啟事務(wù)
BEGIN;
# 修改張三的余額
UPDATE xld_transaction_isolation SET money = money + 50 WHERE NAME = '張三';
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 150
2 李四 80
- 事務(wù)B 再執(zhí)行:
# 設(shè)置事務(wù)隔離級(jí)別為:讀未提交
SET SESSION transaction_isolation = 'read-uncommitted';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
# 開(kāi)啟事務(wù)
BEGIN;
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 150
2 李四 80
**此時(shí)可以看到在 事務(wù)B 中讀取了到 事務(wù)A 中 修改了但未提交 的數(shù)據(jù)。這時(shí)就出現(xiàn)了 臟讀 問(wèn)題。 **
2. 演示:避免臟讀 - (read-committed)
- 事務(wù)A 先執(zhí)行:
# 設(shè)置事務(wù)隔離級(jí)別為:讀已提交
SET SESSION transaction_isolation = 'read-committed';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
# 開(kāi)啟事務(wù)
BEGIN;
# 修改張三的余額
UPDATE xld_transaction_isolation SET money = money + 50 WHERE NAME = '張三';
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 150
2 李四 80
- 事務(wù)B 再執(zhí)行:
# 設(shè)置事務(wù)隔離級(jí)別為:讀已提交
SET SESSION transaction_isolation = 'read-committed';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
# 開(kāi)啟事務(wù)
BEGIN;
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 100
2 李四 80
**此時(shí)可以看到在 事務(wù)B 中并沒(méi)有讀取到 事務(wù)A 中 修改了但未提交 的數(shù)據(jù)。避免了 臟讀 問(wèn)題。 **
- 之后 事務(wù)A 提交修改的數(shù)據(jù):
.......
# 提交
COMMIT;
- 隨之在 事務(wù)B 中再次執(zhí)行查詢:
.......
# 查詢數(shù)據(jù)
SELECT * FROM xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 150
2 李四 80
此時(shí)可以看到 事務(wù)B 中讀取到了 事務(wù)A 中 修改了并提交 的數(shù)據(jù)。
這時(shí)我們可能明顯的看到在 事務(wù)B 中 ,兩次查詢的值是不同的,這時(shí)就出現(xiàn)了 不可重復(fù)讀 問(wèn)題。
3. 演示:避免不可重復(fù)讀 - (repeatable-read)
- 事務(wù)A 先執(zhí)行
# 設(shè)置事務(wù)的隔離級(jí)別為:可重復(fù)讀
SET SESSION TRANSACTION_ISOLATION = 'repeatable-read';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
#開(kāi)啟事務(wù)
BEGIN;
# 查詢數(shù)據(jù) - 事務(wù)B 未提交修改的數(shù)據(jù)之前
SELECT * from xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 100
2 李四 80
- 事務(wù)B 再執(zhí)行
# 設(shè)置事務(wù)的隔離級(jí)別為:可重復(fù)讀
SET SESSION TRANSACTION_ISOLATION = 'repeatable-read';
# 查看事務(wù)的隔離級(jí)別
SELECT @@transaction_isolation;
# 開(kāi)啟事務(wù)
BEGIN;
# 修改張三余額
UPDATE xld_transaction_isolation SET money = money - 50 where name = '張三';
# 查詢數(shù)據(jù)
SELECT * from xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 50
2 李四 80
# 提交事務(wù)
COMMIT;
# 查詢數(shù)據(jù)
SELECT * from xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 50
2 李四 80
- 之后 事務(wù)A 再次查詢數(shù)據(jù)
.......
# 查詢數(shù)據(jù) - 事務(wù)B 提交了修改的數(shù)據(jù)后
SELECT * from xld_transaction_isolation;
# 查詢的結(jié)果為:
id name monry
1 張三 100
2 李四 80
# 提交事務(wù)
COMMIT;
此時(shí)可以看到在 事務(wù)A 中并沒(méi)有讀取到了 事務(wù)B 中 修改了并提交 的數(shù)據(jù),避免了 不可重復(fù)讀 的問(wèn)題。這時(shí)在 事務(wù)A 中讀取的數(shù)據(jù)是不受 其他事務(wù) 影響的。
4. 演示 - 幻讀
# 自己想辦法吧!
... 通過(guò)鎖(獨(dú)占鎖)來(lái)解決 幻讀 的問(wèn)題。后續(xù)章節(jié)會(huì)講到!
4. 事務(wù)的常見(jiàn)分類
從事務(wù)理論的角度來(lái)看,可以把事務(wù)分為以下幾種類型:
- 扁平事務(wù)
- 帶有保存點(diǎn)的扁平事務(wù)
- 鏈?zhǔn)聞?wù)
- 嵌套事務(wù)
- 分布式事務(wù)
下面分別介紹這幾種類型:
- 扁平事務(wù)
扁平事務(wù) 是事務(wù)類型中最簡(jiǎn)單的一種,也是使用最頻繁的事務(wù),在扁平事務(wù)中,所有操作都處于同一層次,由 start transaction 或者 begin 來(lái)開(kāi)啟,commit 或者 rollback 結(jié)束,其間的操作是原子的,要么都執(zhí)行,要么都回滾。因此,扁平事務(wù)是應(yīng)用程序成為原子操作的基本組成模塊。
扁平事務(wù)的三種結(jié)果:
- 事務(wù)成功完成。
- 應(yīng)用程序要求停止事務(wù)。比如應(yīng)用程序在捕獲到異常時(shí)會(huì)回滾事務(wù)。
- 外界因素強(qiáng)制終止事務(wù)。比如連接超時(shí)或連接斷開(kāi)。
- 帶有保存點(diǎn)的扁平事務(wù)
帶有保存點(diǎn)的扁平事務(wù) 除了支持扁平事務(wù)支持的操作外,還允許在事務(wù)執(zhí)行過(guò)程中回滾到同一事務(wù)中較早的一個(gè)狀態(tài),這是因?yàn)槟承┦聞?wù)可能在執(zhí)行過(guò)程中出現(xiàn)了錯(cuò)誤并不會(huì)導(dǎo)致所有的操作都無(wú)效,放棄整個(gè)事務(wù)不合乎要求,開(kāi)銷太大。
保存點(diǎn)(savepoint)用來(lái)通知事務(wù)系統(tǒng)應(yīng)該記住事務(wù)當(dāng)前的狀態(tài),以方便發(fā)送錯(cuò)誤時(shí),事務(wù)能回到保存點(diǎn)當(dāng)時(shí)的狀態(tài)。對(duì)于扁平的事務(wù)來(lái)說(shuō),隱式的設(shè)置了一個(gè)保存點(diǎn),然而在整個(gè)事務(wù)中,只有這一個(gè)保存點(diǎn),因此,回滾只能回滾到事務(wù)開(kāi)始的狀態(tài)。
- 鏈?zhǔn)聞?wù)
鏈?zhǔn)聞?wù) 是指一個(gè)事務(wù)由多個(gè)子事務(wù)鏈?zhǔn)浇M成,它可以被視為保存點(diǎn)模式的一個(gè)變種。
帶有保存點(diǎn)的扁平事務(wù),當(dāng)發(fā)生系統(tǒng)崩潰時(shí),所有的保存點(diǎn)都將消失,這意味著當(dāng)進(jìn)行恢復(fù)時(shí),事務(wù)需要從開(kāi)始處重新執(zhí)行,而不能從最近的一個(gè)保存點(diǎn)繼續(xù)執(zhí)行。
鏈?zhǔn)聞?wù)的思想是:在提交一個(gè)事務(wù)時(shí),釋放不需要的數(shù)據(jù)對(duì)象,將必要的處理上下文隱式地傳給下一個(gè)要開(kāi)始的事務(wù),前一個(gè)子事務(wù)的提交操作和下一個(gè)子事務(wù)的開(kāi)始操作合并成一個(gè)原子操作,這意味著下一個(gè)事務(wù)將看到上一個(gè)事務(wù)的結(jié)果,就好像在一個(gè)事務(wù)中進(jìn)行一樣。這樣,在提交子事務(wù)就可以釋放不需要的數(shù)據(jù)對(duì)象,而不必等到整個(gè)事務(wù)完成后才釋放。
鏈?zhǔn)聞?wù)與帶有保存點(diǎn)的扁平事務(wù)的不同之處在于:
- 帶有保存點(diǎn)的扁平事務(wù)能回滾到任意正確的保存點(diǎn),而鏈?zhǔn)聞?wù)中的回滾僅限于當(dāng)前事務(wù),即只能恢復(fù)到最近的一個(gè)保存點(diǎn)。
- 對(duì)于鎖的處理,兩者也不相同,鏈?zhǔn)聞?wù)在執(zhí)行 commit 后即釋放了當(dāng)前所持有的鎖,而帶有保存點(diǎn)的扁平事務(wù)不影響迄今為止所持有的鎖。
- 嵌套事務(wù)
嵌套事務(wù) 是一個(gè)層次結(jié)構(gòu)框架,由一個(gè)頂層事務(wù)控制著各個(gè)層次的事務(wù),頂層事務(wù)之下嵌套的事務(wù)稱為子事務(wù),其控制著每一個(gè)局部的變換,子事務(wù)本身也可以是嵌套事務(wù)。因此,嵌套事務(wù)的層次結(jié)構(gòu)可以看成是一棵樹
- 分布式事務(wù)
分布式事務(wù) 通常是在一個(gè)分布式環(huán)境下運(yùn)行的扁平事務(wù),因此,需要根據(jù)數(shù)據(jù)所在位置訪問(wèn)網(wǎng)絡(luò)中不同節(jié)點(diǎn)的數(shù)據(jù)庫(kù)資源。
例如:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-760681.html
一個(gè)銀行用戶從招商銀行的賬戶向工商銀行的賬戶轉(zhuǎn)賬 1000 元,這里需要用到分布式事務(wù),因?yàn)椴荒軆H調(diào)用某一家銀行的數(shù)據(jù)庫(kù)就完成任何。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-760681.html
到了這里,關(guān)于MySQL 事務(wù)的基礎(chǔ)知識(shí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!