MySQL探秘之旅:繞不開的數(shù)據(jù)庫事務(wù)
怎么強(qiáng)調(diào)數(shù)據(jù)庫事務(wù)的重要性都不為過,如果你還不知道、還沒有使用過事務(wù),那么你可能連初級程序員都算不上。
一、什么是數(shù)據(jù)庫事務(wù)
數(shù)據(jù)庫事務(wù)是用戶定義的一個(gè)數(shù)據(jù)庫操作序列,這些操作要么全做要么全不做,是一個(gè)不可分割的工作單位。
事務(wù)具有四個(gè)特性:原子性(Atomicity),一致性(Consistency),隔離性(Isolation)和持久性(Durability),簡稱ACID特性。
我們以經(jīng)典的轉(zhuǎn)賬例子來分別說明這幾個(gè)特性的詳細(xì)含義:A、B賬戶的余額都是一萬元,然后A賬戶向B賬戶轉(zhuǎn)賬1000元,這實(shí)際上包括了兩步操作,先是A賬戶減去1000元,變成了9000元,而B賬戶加上1000元,變成了11000元。
1. 原子性:事務(wù)中包含的操作要么全都做,要么全都不做。比如上面的轉(zhuǎn)賬例子,要么A賬戶減少1000元且B賬戶增加了1000元,要么兩個(gè)賬戶的余額都沒有改變。
2. 一致性:事務(wù)執(zhí)行的結(jié)果必須是使數(shù)據(jù)庫從一個(gè)一致性狀態(tài)變成另外一個(gè)一致性狀態(tài)。在轉(zhuǎn)賬前,A、B賬戶的余額是一萬元,合計(jì)是兩萬元,轉(zhuǎn)賬后兩者的賬戶合計(jì)也必須是兩萬元,如果轉(zhuǎn)賬所涉及的兩步操作有一步失敗,比如A賬戶減去1000元沒有成功,但B賬戶卻加上了1000元,那么事務(wù)結(jié)束后,兩者的余額合計(jì)就變成了兩萬一千元,這時(shí)數(shù)據(jù)庫就處于不一致的狀態(tài)了。
3. 隔離性:多個(gè)事務(wù)可以并發(fā)執(zhí)行,但事務(wù)之間不能相互干擾,以確保一個(gè)事務(wù)的操作不會影響到另外一個(gè)事務(wù)。
4. 持久性:指事務(wù)一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的影響是永久性的,接下來的其他操作或者故障不應(yīng)該對執(zhí)行結(jié)果有任何影響。
二、MySQL中的事務(wù)
在MySQL的架構(gòu)體系中,最下面一層是存儲引擎層,它負(fù)責(zé)數(shù)據(jù)的存儲和提取,通過命令show engines可以查看當(dāng)前服務(wù)器支持的存儲引擎:
由上圖可以看出,只有InnoDB存儲引擎才支持事務(wù),而它也是MySQL 5.7默認(rèn)的選項(xiàng),其它存儲引擎都不支持事務(wù)。NDB Cluster也支持事務(wù),它是基于內(nèi)存的存儲引擎,需要另行安裝,本文暫不討論。
MySQL默認(rèn)采用自動提交(AUTOCOMMIT)模式,即使沒有手動開啟事務(wù)(START TRANSACTION),MySQL也會默認(rèn)將當(dāng)前的每個(gè)SQL語句當(dāng)成一個(gè)事務(wù)進(jìn)行提交,可通過以下語句查詢當(dāng)前連接的自動提交模式:
SHOW VARIABLES LIKE 'AUTOCOMMIT';
從以上語句的執(zhí)行結(jié)果可以看到,這個(gè)參數(shù)默認(rèn)是ON,也就是開啟了自動提交模式:
圖片
如果需要關(guān)閉自動提交模式,則把它設(shè)置為零即可,1或者ON表示啟用,0或者OFF表示關(guān)閉:
SET AUTOCOMMIT=0;
自動模式關(guān)閉以后,就需要手動開啟并提交或者回滾事務(wù)。
三、MySQL事務(wù)控制語句
BEGIN或START TRANSACTION:開啟一個(gè)新事務(wù)
COMMIT:提交當(dāng)前事務(wù),讓SQL語句對數(shù)據(jù)庫的修改成為永久性的
ROLLBACK:回滾當(dāng)前事務(wù),撤銷對數(shù)據(jù)庫的修改
SAVEPOINT identifier:保存點(diǎn),可以使用它回滾部分指定事務(wù),從而可以更精細(xì)的控制事務(wù),identifier為保存點(diǎn)的名稱
ROLLBACK TO SAVEPOINT:事務(wù)回滾到指定的保存點(diǎn)
RELEASE SAVEPOINT:刪除事務(wù)的保存點(diǎn)
SET TRANSACTION:用于指定事務(wù)的特性,比如隔離級別、訪問模式(讀寫/只讀)等。
我們用一個(gè)簡單的例子來演示一下部分控制語句的用法,特別是關(guān)于保存點(diǎn)的用法,相對來講,它可能有點(diǎn)陌生。我們創(chuàng)建一個(gè)非常簡單的表,并且往里面插入幾行數(shù)據(jù):
CREATE TABLE `t_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `t_employee` (`name`) VALUES ('Tom');
INSERT INTO `t_employee` (`name`) VALUES ('John');
INSERT INTO `t_employee` (`name`) VALUES ('Hans');
# 開啟一個(gè)事務(wù)
START TRANSACTION;
# 更新id為1的name字段
UPDATE t_employee SET name='full stack' WHERE id = 1;
# 確認(rèn)更新成功
SELECT * FROM t_employee WHERE id = 1;
# 設(shè)置一個(gè)保存點(diǎn),名稱為s1
SAVEPOINT s1;
# 再次更新id為1的name字段
UPDATE t_employee SET name='full stack new' WHERE id = 1;
SELECT * FROM t_employee WHERE id = 1;
# 回滾事務(wù)到保存點(diǎn)s1
ROLLBACK TO s1;
# 會發(fā)現(xiàn)最后一次更新沒有成功,因?yàn)樗换貪L了
SELECT * FROM t_employee WHERE id = 1
如果執(zhí)行上面的SQL有錯(cuò)誤,請按照提示刪除多余的換行符。
四、事務(wù)隔離級別
上面我們提到過,事務(wù)有四大特性,其中一個(gè)是隔離性,在并發(fā)執(zhí)行事務(wù)的情況,可能會存在事務(wù)之間相互干擾的情況,這時(shí)候就需要指定合適的隔離級別,來保證程序運(yùn)行的正確性。事務(wù)隔離級別是指多個(gè)事務(wù)同時(shí)操作數(shù)據(jù)庫時(shí),事務(wù)之間相互隔離的程度,SQL 92規(guī)范有四種隔離級別,MySQL InnoDB存儲引擎也支持這四種級別,它們分別是:讀未提交,讀已提交,可重復(fù)讀,串行化。
圖片
下面我們用實(shí)例演示這四種隔離級別,首先創(chuàng)建一個(gè)表并接入記錄:
CREATE TABLE `checking` (
`customer_id` int(11) NOT NULL,
`customer_name` varchar(255) DEFAULT NULL,
`balance` decimal(16,4) DEFAULT NULL,
`create_date` datetime DEFAULT NULL,
`update_date` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_id`),
KEY `idx_customer_id` (`customer_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of checking
-- ----------------------------
INSERT INTO `checking` VALUES ('1002', 'Jane', '500.0000', '2024-11-01 09:57:08', '2024-11-08 16:34:09');
INSERT INTO `checking` VALUES ('1003', 'Tom', '300.0000', '2024-11-01 09:57:08', '2024-11-08 16:34:09');
INSERT INTO `checking` VALUES ('1004', 'John', '600.0000', '2024-11-01 09:57:08', '2024-11-08 16:34:09');
1.讀未提交
Step1:事務(wù)A
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為讀未提交
- 開始事務(wù)
- 查詢客戶1002的余額為500
Step2:事務(wù)B
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為讀未提交
- 開始事務(wù)
- 更新客戶1002的余額為800
- 事務(wù)還沒有提交
Step3:事務(wù)A
圖片
- 再次查詢客戶1002的余額為,值800
- 盡管事務(wù)B還沒有提交
- 如果事務(wù)B回滾了,那么事務(wù)A查詢到的信息就臟數(shù)據(jù)
2.讀已提交
Step1:事務(wù)A
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為讀已提交
- 開始事務(wù)
- 查詢客戶1002的余額為500
Step2:事務(wù)B
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為讀已提交
- 開始事務(wù)
- 更新客戶1002的余額為800
- 事務(wù)還沒有提交
Step3:事務(wù)A
圖片
- 再次查詢客戶1002的余額,值仍然為500
- 事務(wù)B還沒有提交,所以解決了臟讀的問題
Step4:事務(wù)B
圖片
- 提交事務(wù)
- 確認(rèn)余額為800
Step5:事務(wù)A
圖片
- 再次查詢客戶1002的余額,值變成了800
- 事務(wù)A并還沒有提交,但這次讀到的數(shù)據(jù)不一樣了,產(chǎn)生不可重復(fù)讀的問題
3.可重復(fù)讀
Step1:事務(wù)A
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為可重復(fù)讀
- 開始事務(wù)
- 查詢表中的所有記錄,一共三條
Step2:事務(wù)B
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為可重復(fù)讀
- 開始事務(wù)
- 向表中插入一條記錄,customer_id=1100
Step3:事務(wù)A
圖片
- 不管事務(wù)B有沒有提交,都查不到事務(wù)B插入的數(shù)據(jù)
- 避免了不可重復(fù)讀的問題,通過MVCC機(jī)制實(shí)現(xiàn)
Step4:事務(wù)A
圖片
- 把customer_id為1100的余額更新為460
- 再次查詢,發(fā)現(xiàn)表中有了四條記錄,產(chǎn)生了幻讀
- 可以通過加鎖讀取最新的數(shù)據(jù)
4.串行化
Step1:事務(wù)A
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為串行化
- 開始事務(wù)
- 查詢表中的所有記錄,一共三條
Step2:事務(wù)B
圖片
- 設(shè)置當(dāng)前session的事務(wù)隔離級別為串行化
- 開始事務(wù)
- 向表中插入一條新的記錄
- 最后提示超時(shí)的錯(cuò)誤
- 串行化會強(qiáng)制把事務(wù)按照串行的方式執(zhí)行,效率很低
圖片
四、小結(jié)
以上就是關(guān)于MySQL數(shù)據(jù)庫事務(wù)的內(nèi)容,從事務(wù)的含義和特性開始講解,接下來是MySQL對于事務(wù)的控制語句,最后再詳細(xì)說明數(shù)據(jù)庫的隔離級別,并用實(shí)例演示它的含義。
不論是日常編程,還是在面試當(dāng)中,數(shù)據(jù)庫事務(wù)都是繞不開的一個(gè)話題,所以有必要深入掌握,特別是它的隔離級別,也是經(jīng)常被經(jīng)常問起的問題。