1 數(shù)據(jù)庫事務(wù)概述
1.1 存儲引擎支持情況
SHOW ENGINES
命令來查看當(dāng)前 MySQL 支持的存儲引擎都有哪些,以及這些存儲引擎是否支持事務(wù)。
能看出在 MySQL 中,只有InnoDB 是支持事務(wù)的。
1.2 基本概念
事務(wù)
:一組邏輯操作單元,使數(shù)據(jù)從一種狀態(tài)變換到另一種狀態(tài)。事務(wù)處理的原則
:保證所有事務(wù)都作為 一個工作單元
來執(zhí)行,即使出現(xiàn)了故障,都不能改變這種執(zhí)行方式。當(dāng)在一個事務(wù)中執(zhí)行多個操作時,要么所有的事務(wù)都被提交( commit
),那么這些修改就 永久
地保存下來;要么數(shù)據(jù)庫管理系統(tǒng)將 放棄
所作的所有 修改
,整個事務(wù)回滾( rollback
)到最初狀態(tài)。
1.3 事務(wù)的ACID特性
-
原子性(atomicity):
原子性是指事務(wù)是一個不可分割的工作單位,要么全部提交,要么全部失敗回滾。 -
一致性(consistency):
根據(jù)定義,一致性是指事務(wù)執(zhí)行前后,數(shù)據(jù)從一個合法性狀態(tài)
變換到另外一個合法性狀態(tài)
。這種狀態(tài)是語義上
的而不是語法上的,跟具體的業(yè)務(wù)有關(guān)。
那什么是合法的數(shù)據(jù)狀態(tài)呢?滿足 預(yù)定的約束
的狀態(tài)就叫做合法的狀態(tài)。通俗一點,這狀態(tài)是由你自己來定義的(比如滿足現(xiàn)實世界中的約束)。滿足這個狀態(tài),數(shù)據(jù)就是一致的,不滿足這個狀態(tài),數(shù)據(jù)就是不一致的!如果事務(wù)中的某個操作失敗了,系統(tǒng)就會自動撤銷當(dāng)前正在執(zhí)行的事務(wù),返回到事務(wù)操作之前的狀態(tài)。
-
隔離型(isolation):
事務(wù)的隔離性是指一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾
,即一個事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對 并發(fā) 的其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個事務(wù)之間不能互相干擾。
如果無法保證隔離性會怎么樣?假設(shè)A賬戶有200元,B賬戶0元。A賬戶往B賬戶轉(zhuǎn)賬兩次,每次金額為50元,分別在兩個事務(wù)中執(zhí)行。如果無法保證隔離性,會出現(xiàn)下面的情形:
UPDATE accounts SET money = money - 50 WHERE NAME = 'AA';
UPDATE accounts SET money = money + 50 WHERE NAME = 'BB';
-
持久性(durability):
持久性是指一個事務(wù)一旦被提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的
,接下來的其他操作和數(shù)據(jù)庫故障不應(yīng)該對其有任何影響。
持久性是通過事務(wù)日志
來保證的。日志包括了重做日志
和回滾日志
。當(dāng)我們通過事務(wù)對數(shù)據(jù)進行修改的時候,首先會將數(shù)據(jù)庫的變化信息記錄到重做日志中,然后再對數(shù)據(jù)庫中對應(yīng)的行進行修改。這樣做的好處是,即使數(shù)據(jù)庫系統(tǒng)崩潰,數(shù)據(jù)庫重啟后也能找到?jīng)]有更新到數(shù)據(jù)庫系統(tǒng)中的重做日志,重新執(zhí)行,從而使事務(wù)具有持久性。
1.4 事務(wù)的狀態(tài)
我們現(xiàn)在知道 事務(wù)
是一個抽象的概念,它其實對應(yīng)著一個或多個數(shù)據(jù)庫操作,MySQL根據(jù)這些操作所執(zhí)行的不同階段把 事務(wù)
大致劃分成幾個狀態(tài):
-
活動的(active)
事務(wù)對應(yīng)的數(shù)據(jù)庫操作正在執(zhí)行過程中時,我們就說該事務(wù)處在活動的
狀態(tài)。 -
部分提交的(partially committed)
當(dāng)事務(wù)中的最后一個操作執(zhí)行完成,但由于操作都在內(nèi)存中執(zhí)行,所造成的影響并 沒有刷新到磁盤時,我們就說該事務(wù)處在 部分提交的 狀態(tài)。 -
失敗的(failed)
當(dāng)事務(wù)處在活動的
或者部分提交的
狀態(tài)時,可能遇到了某些錯誤(數(shù)據(jù)庫自身的錯誤、操作系統(tǒng)錯誤或者直接斷電等)而無法繼續(xù)執(zhí)行,或者人為的停止當(dāng)前事務(wù)的執(zhí)行,我們就說該事務(wù)處在失敗的
狀態(tài)。 -
中止的(aborted)
如果事務(wù)執(zhí)行了一部分而變?yōu)?失敗的
狀態(tài),那么就需要把已經(jīng)修改的事務(wù)中的操作還原到事務(wù)執(zhí)行前的狀態(tài)。換句話說,就是要撤銷失敗事務(wù)對當(dāng)前數(shù)據(jù)庫造成的影響。我們把這個撤銷的過程稱之為回滾
。當(dāng)回滾
操作執(zhí)行完畢時,也就是數(shù)據(jù)庫恢復(fù)到了執(zhí)行事務(wù)之前的狀態(tài),我們就說該事務(wù)處在了中止的
狀態(tài)。
舉例:UPDATE accounts SET money = money - 50 WHERE NAME = 'AA'; UPDATE accounts SET money = money + 50 WHERE NAME = 'BB';
-
提交的(committed)
當(dāng)一個處在部分提交的
狀態(tài)的事務(wù)將修改過的數(shù)據(jù)都同步到磁盤
上之后,我們就可以說該事務(wù)處在了提交的
狀態(tài)。
一個基本的狀態(tài)轉(zhuǎn)換圖如下所示:
2 如何使用事務(wù)
使用事務(wù)有兩種方式,分別為 顯式事務(wù)
和 隱式事務(wù)
。
2.1 顯示事務(wù)
步驟1: START TRANSACTION
或者 BEGIN
,作用是顯式開啟一個事務(wù)。
mysql> BEGIN;
#或者
mysql> START TRANSACTION;
START TRANSACTION
語句相較于 BEGIN
特別之處在于,后邊能跟隨幾個 修飾符
:
① READ ONLY
:標(biāo)識當(dāng)前事務(wù)是一個 只讀事務(wù)
,也就是屬于該事務(wù)的數(shù)據(jù)庫操作只能讀取數(shù)據(jù),而不能修改數(shù)據(jù)。
② READ WRITE
:標(biāo)識當(dāng)前事務(wù)是一個 讀寫事務(wù)
,也就是屬于該事務(wù)的數(shù)據(jù)庫操作既可以讀取數(shù)據(jù),也可以修改數(shù)據(jù)。
③ WITH CONSISTENT SNAPSHOT
:啟動一致性讀。
步驟2:一系列事務(wù)中的操作(主要是DML,不含DDL)
步驟3:提交事務(wù) 或 中止事務(wù)(即回滾事務(wù))
# 提交事務(wù)。當(dāng)提交事務(wù)后,對數(shù)據(jù)庫的修改是永久性的。
mysql> COMMIT;
# 回滾事務(wù)。即撤銷正在進行的所有沒有提交的修改
mysql> ROLLBACK;
# 將事務(wù)回滾到某個保存點。
mysql> ROLLBACK TO [SAVEPOINT]
2.2 隱式事務(wù)
MySQL中有一個系統(tǒng)變量 autocommit
:
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
當(dāng)然,如果我們想關(guān)閉這種 自動提交
的功能,可以使用下邊兩種方法之一:
-
顯式的的使用
START TRANSACTION
或者BEGIN
語句開啟一個事務(wù)。這樣在本次事務(wù)提交或者回滾前會暫時關(guān)閉掉自動提交的功能。 -
把系統(tǒng)變量
autocommit
的值設(shè)置為OFF
,就像這樣:SET autocommit = OFF; #或 SET autocommit = 0;
2.3 隱式提交數(shù)據(jù)的情況
-
數(shù)據(jù)定義語言(Data definition language,縮寫為:DDL)
-
事務(wù)控制或關(guān)于鎖定的語句
- 當(dāng)我們在一個事務(wù)還沒提交或者回滾時就又使用 START TRANSACTION 或者 BEGIN 語句開啟了另一個事務(wù)時,會 隱式的提交 上一個事務(wù)。即:
- 當(dāng)前的 autocommit 系統(tǒng)變量的值為 OFF ,我們手動把它調(diào)為 ON 時,也會 隱式的提交 前邊語句所屬的事務(wù)。
- 使用 LOCK TABLES 、 UNLOCK TABLES 等關(guān)于鎖定的語句也會 隱式的提交 前邊語句所屬的事務(wù)。
-
加載數(shù)據(jù)的語句
-
關(guān)于MySQL復(fù)制的一些語句
-
其它的一些語句
2.4 使用舉例1:提交與回滾
我們看下在 MySQL 的默認(rèn)狀態(tài)下,下面這個事務(wù)最后的處理結(jié)果是什么。
情況1:
CREATE TABLE user(name varchar(20), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO user SELECT '張三';
COMMIT;
BEGIN;
INSERT INTO user SELECT '李四';
INSERT INTO user SELECT '李四';
ROLLBACK;
SELECT * FROM user;
運行結(jié)果(1 行數(shù)據(jù)):
mysql> commit;
Query OK, 0 rows affected (0.00 秒)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 秒)
mysql> INSERT INTO user SELECT '李四';
Query OK, 1 rows affected (0.00 秒)
mysql> INSERT INTO user SELECT '李四';
Duplicate entry '李四' for key 'user.PRIMARY'
mysql> ROLLBACK;
Query OK, 0 rows affected (0.01 秒)
mysql> select * from user;
+--------+
| name |
+--------+
| 張三 |
+--------+
1 行于數(shù)據(jù)集 (0.01 秒)
情況2:
CREATE TABLE user (name varchar(20), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO user SELECT '張三';
COMMIT;
INSERT INTO user SELECT '李四';
INSERT INTO user SELECT '李四';
ROLLBACK;
運行結(jié)果(2 行數(shù)據(jù)):
mysql> SELECT * FROM user;
+--------+
| name |
+--------+
| 張三 |
| 李四 |
+--------+
2 行于數(shù)據(jù)集 (0.01 秒)
情況3:
CREATE TABLE user(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
SET @@completion_type = 1;
BEGIN;
INSERT INTO user SELECT '張三';
COMMIT;
INSERT INTO user SELECT '李四';
INSERT INTO user SELECT '李四';
ROLLBACK;
SELECT * FROM user;
運行結(jié)果(1 行數(shù)據(jù)):
mysql> SELECT * FROM user;
+--------+
| name |
+--------+
| 張三 |
+--------+
1 行于數(shù)據(jù)集 (0.01 秒)
當(dāng)我們設(shè)置 autocommit=0 時,不論是否采用 START TRANSACTION 或者 BEGIN 的方式來開啟事務(wù),都需要用COMMIT 進行提交,讓事務(wù)生效,使用 ROLLBACK 對事務(wù)進行回滾。
當(dāng)我們設(shè)置 autocommit=1 時,每條 SQL語句都會自動進行提交。 不過這時,如果你采用 START TRANSACTION 或者 BEGIN 的方式來顯式地開啟事務(wù),那么這個事務(wù)只有在 COMMIT 時才會生效,在 ROLLBACK 時才會回滾。
3 事務(wù)隔離級別
MySQL是一個 客戶端/服務(wù)器
架構(gòu)的軟件,對于同一個服務(wù)器來說,可以有若干個客戶端與之連接,每個客戶端與服務(wù)器連接上之后,就可以稱為一個會話( Session
)。每個客戶端都可以在自己的會話中向服務(wù)器發(fā)出請求語句,一個請求語句可能是某個事務(wù)的一部分,也就是對于服務(wù)器來說可能同時處理多個事務(wù)。事務(wù)有 隔離性
的特性,理論上在某個事務(wù) 對某個數(shù)據(jù)進行訪問
時,其他事務(wù)應(yīng)該進行 排隊
,當(dāng)該事務(wù)提交之后,其他事務(wù)才可以繼續(xù)訪問這個數(shù)據(jù)。但是這樣對 性能影響太大
,我們既想保持事務(wù)的隔離性,又想讓服務(wù)器在處理訪問同一數(shù)據(jù)的多個事務(wù)時性能盡量高些
,那就看二者如何權(quán)衡取舍了。
3.1 數(shù)據(jù)準(zhǔn)備
我們需要創(chuàng)建一個表:
CREATE TABLE student (
studentno INT,
name VARCHAR(20),
class varchar(20),
PRIMARY KEY (studentno)
) Engine=InnoDB CHARSET=utf8;
然后向這個表里插入一條數(shù)據(jù):
INSERT INTO student VALUES(1, '小谷', '1班');
現(xiàn)在表里的數(shù)據(jù)就是這樣的:
mysql> select * from student;
+-----------+--------+-------+
| studentno | name | class |
+-----------+--------+-------+
| 1 | 小谷 | 1班 |
+-----------+--------+-------+
1 row in set (0.00 sec)
3.2 數(shù)據(jù)并發(fā)問題
針對事務(wù)的隔離性和并發(fā)性,我們怎么做取舍呢?先看一下訪問相同數(shù)據(jù)的事務(wù)在 不保證串行執(zhí)行
(也就是執(zhí)行完一個再執(zhí)行另一個)的情況下可能會出現(xiàn)哪些問題:
1. 臟寫( Dirty Write )
對于兩個事務(wù) Session A、Session B,如果事務(wù)Session A 修改了
另一個 未提交
事務(wù)Session B 修改過
的數(shù)據(jù),那就意味著發(fā)生了 臟寫
2. 臟讀( Dirty Read )
對于兩個事務(wù) Session A、Session B,Session A 讀取
了已經(jīng)被 Session B 更新
但還 沒有被提交
的字段。之后若 Session B 回滾
,Session A 讀取 的內(nèi)容就是 臨時且無效
的。
Session A和Session B各開啟了一個事務(wù),Session B中的事務(wù)先將studentno列為1的記錄的name列更新為’張三’,然后Session A中的事務(wù)再去查詢這條studentno為1的記錄,如果讀到列name的值為’張三’,而Session B中的事務(wù)稍后進行了回滾,那么Session A中的事務(wù)相當(dāng)于讀到了一個不存在的數(shù)據(jù),這種現(xiàn)象就稱之為 臟讀
。
3. 不可重復(fù)讀( Non-Repeatable Read )
對于兩個事務(wù)Session A、Session B,Session A 讀取
了一個字段,然后 Session B 更新
了該字段。 之后Session A 再次讀取
同一個字段, 值就不同
了。那就意味著發(fā)生了不可重復(fù)讀。
我們在Session B中提交了幾個 隱式事務(wù)
(注意是隱式事務(wù),意味著語句結(jié)束事務(wù)就提交了),這些事務(wù)都修改了studentno列為1的記錄的列name的值,每次事務(wù)提交之后,如果Session A中的事務(wù)都可以查看到最新的值,這種現(xiàn)象也被稱之為 不可重復(fù)讀
。
4. 幻讀( Phantom )
對于兩個事務(wù)Session A、Session B, Session A 從一個表中 讀取
了一個字段, 然后 Session B 在該表中 插入 了一些新的行。 之后, 如果 Session A 再次讀取
同一個表, 就會多出幾行。那就意味著發(fā)生了幻讀。
Session A中的事務(wù)先根據(jù)條件 studentno > 0這個條件查詢表student,得到了name列值為’張三’的記錄;之后Session B中提交了一個 隱式事務(wù)
,該事務(wù)向表student中插入了一條新記錄;之后Session A中的事務(wù)再根據(jù)相同的條件 studentno > 0查詢表student,得到的結(jié)果集中包含Session B中的事務(wù)新插入的那條記錄,這種現(xiàn)象也被稱之為 幻讀
。我們把新插入的那些記錄稱之為 幻影記錄
。
3.3 SQL中的四種隔離級別
上面介紹了幾種并發(fā)事務(wù)執(zhí)行過程中可能遇到的一些問題,這些問題有輕重緩急之分,我們給這些問題按照嚴(yán)重性來排一下序:
臟寫 > 臟讀 > 不可重復(fù)讀 > 幻讀
我們愿意舍棄一部分隔離性來換取一部分性能在這里就體現(xiàn)在:設(shè)立一些隔離級別,隔離級別越低,并發(fā)問題發(fā)生的就越多。 SQL標(biāo)準(zhǔn)
中設(shè)立了4個 隔離級別
:
-
READ UNCOMMITTED
:讀未提交,在該隔離級別,所有事務(wù)都可以看到其他未提交事務(wù)的執(zhí)行結(jié)果。不能避免臟讀、不可重復(fù)讀、幻讀。 -
READ COMMITTED
:讀已提交,它滿足了隔離的簡單定義:一個事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變。這是大多數(shù)數(shù)據(jù)庫系統(tǒng)的默認(rèn)隔離級別(但不是MySQL默認(rèn)的)??梢员苊馀K讀,但不可重復(fù)讀、幻讀問題仍然存在。 -
REPEATABLE READ
:可重復(fù)讀,事務(wù)A在讀到一條數(shù)據(jù)之后,此時事務(wù)B對該數(shù)據(jù)進行了修改并提交,那么事務(wù)A再讀該數(shù)據(jù),讀到的還是原來的內(nèi)容??梢员苊馀K讀、不可重復(fù)讀,但幻讀問題仍然存在。這是MySQL的默認(rèn)隔離級別。 -
SERIALIZABLE
:可串行化,確保事務(wù)可以從一個表中讀取相同的行。在這個事務(wù)持續(xù)期間,禁止其他事務(wù)對該表執(zhí)行插入、更新和刪除操作。所有的并發(fā)問題都可以避免,但性能十分低下。能避 免臟讀、不可重復(fù)讀和幻讀。
SQL標(biāo)準(zhǔn)
中規(guī)定,針對不同的隔離級別,并發(fā)事務(wù)可以發(fā)生不同嚴(yán)重程度的問題,具體情況如下:臟寫
怎么沒涉及到?因為臟寫這個問題太嚴(yán)重了,不論是哪種隔離級別,都不允許臟寫的情況發(fā)生。
不同的隔離級別有不同的現(xiàn)象,并有不同的鎖和并發(fā)機制,隔離級別越高,數(shù)據(jù)庫的并發(fā)性能就越差,4種事務(wù)隔離級別與并發(fā)性能的關(guān)系如下:
3.4 MySQL支持的四種隔離級別
MySQL的默認(rèn)隔離級別為REPEATABLE READ,我們可以手動修改一下事務(wù)的隔離級別。
# 查看隔離級別,MySQL 5.7.20的版本之前:
mysql> SHOW VARIABLES LIKE 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
# MySQL 5.7.20版本之后,引入transaction_isolation來替換tx_isolation
# 查看隔離級別,MySQL 5.7.20的版本及之后:
mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.02 sec)
#或者不同MySQL版本中都可以使用的:
SELECT @@transaction_isolation;
3.5 如何設(shè)置事務(wù)的隔離級別
通過下面的語句修改事務(wù)的隔離級別:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔離級別;
#其中,隔離級別格式:
> READ UNCOMMITTED
> READ COMMITTED
> REPEATABLE READ
> SERIALIZABLE
或者:
SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔離級別'
#其中,隔離級別格式:
> READ-UNCOMMITTED
> READ-COMMITTED
> REPEATABLE-READ
> SERIALIZABLE
關(guān)于設(shè)置時使用GLOBAL或SESSION的影響:
-
使用
GLOBAL
關(guān)鍵字(在全局范圍影響):SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE; #或 SET GLOBAL TRANSACTION_ISOLATION = 'SERIALIZABLE';
則:
- 當(dāng)前已經(jīng)存在的會話無效
- 只對執(zhí)行完該語句之后產(chǎn)生的會話起作用
-
使用
SESSION
關(guān)鍵字(在會話范圍影響):SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; #或 SET SESSION TRANSACTION_ISOLATION = 'SERIALIZABLE';
則:
- 對當(dāng)前會話的所有后續(xù)的事務(wù)有效
- 如果在事務(wù)之間執(zhí)行,則對后續(xù)的事務(wù)有效
- 該語句可以在已經(jīng)開啟的事務(wù)中間執(zhí)行,但不會影響當(dāng)前正在執(zhí)行的事務(wù)
小結(jié):
數(shù)據(jù)庫規(guī)定了多種事務(wù)隔離級別,不同隔離級別對應(yīng)不同的干擾程度,隔離級別越高,數(shù)據(jù)一致性
就越好,但并發(fā)性越弱。
3.6 不同隔離級別舉例
演示1. 讀未提交之臟讀
設(shè)置隔離級別為未提交讀:
事務(wù)1和事務(wù)2的執(zhí)行流程如下:
演示2:讀已提交設(shè)置隔離級別為可重復(fù)讀,事務(wù)的執(zhí)行流程如下:
演示4:幻讀文章來源:http://www.zghlxwxcb.cn/news/detail-446410.html
4. 事務(wù)的常見分類
從事務(wù)理論的角度來看,可以把事務(wù)分為以下幾種類型:文章來源地址http://www.zghlxwxcb.cn/news/detail-446410.html
- 扁平事務(wù)(Flat Transactions)
- 帶有保存點的扁平事務(wù)(Flat Transactions with Savepoints)
- 鏈?zhǔn)聞?wù)(Chained Transactions)
- 嵌套事務(wù)(Nested Transactions)
- 分布式事務(wù)(Distributed Transactions)
到了這里,關(guān)于MySQL基礎(chǔ)(三十二)事務(wù)基礎(chǔ)知識的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!