什么是事務(wù)?
用 MySQL 官方的一句話來(lái)描述事務(wù)是什么?MySQL 事務(wù)主要用于處理操作量大,復(fù)雜度高的數(shù)據(jù)。那何為數(shù)據(jù)量大?何為復(fù)雜度高呢?我用我自己的理解來(lái)描述一下吧。事務(wù)其實(shí)就是 MySQL 中處理數(shù)據(jù)的一種方式,主要用在數(shù)據(jù)完整性高,數(shù)據(jù)之間依賴性大的情況下的一種數(shù)據(jù)處理方式。舉個(gè)例子,小張向小李的銀行卡打 200 塊錢(qián),在小張點(diǎn)擊了確認(rèn)轉(zhuǎn)賬的按鈕時(shí),系統(tǒng)突然崩潰了。會(huì)出現(xiàn)這樣幾中不正確的情況:
1. 小張的錢(qián)打到小李的賬戶上,但是自己的賬戶上的錢(qián)沒(méi)被扣.
2. 小張的錢(qián)打沒(méi)到小李的賬戶上了,但是自己賬戶上的錢(qián)被扣.
這樣的業(yè)務(wù)場(chǎng)景就需要 MySQL 事務(wù)保持,即使機(jī)器出故障的情況下,數(shù)據(jù)仍然是正確的.
事務(wù)使用的條件
MySQL 要使用事務(wù),需要 MySQL 中的存儲(chǔ)引擎支持。現(xiàn)目前 MySQL 內(nèi)置的存儲(chǔ)引擎支持事務(wù)的有 InnoDB、NDB cluster, 第三方的存儲(chǔ)引擎有 PBXT 和 XtrDB.
事務(wù)有什么特點(diǎn)?
MySQL 中的事務(wù)有如下幾個(gè)特點(diǎn) (ACID):
原子性 (atomicity):
一個(gè)事務(wù)必須被作為一個(gè)不可分割的最小工作單元,每個(gè)事務(wù)中的所有操作必須要么成功,或者要么失敗,永遠(yuǎn)不可能一些操作失敗,一些操作成功,這就是所謂的原子性的概念.
一致性 (consistency):
一致性就像上面舉的一個(gè)例子一樣,當(dāng)發(fā)生異常情況下,數(shù)據(jù)仍然是正確的。就是說(shuō)當(dāng)一個(gè)事務(wù)執(zhí)行失敗了,數(shù)據(jù)之間是不會(huì)受異常的情況而影響,永遠(yuǎn)保持著他的正確性.
隔離性 (isolation):
當(dāng)一個(gè)事務(wù)還未提交,每個(gè)事務(wù)之間是相互隔離的,互補(bǔ)受到影響.
持久性 (durability):
當(dāng)一個(gè)事務(wù)進(jìn)行提交之后,發(fā)生的變化就會(huì)永遠(yuǎn)保存在數(shù)據(jù)庫(kù)中.
事務(wù)的隔離級(jí)別
在談及到 MySQL 的隔離性的特點(diǎn),就不得不說(shuō)說(shuō)隔離性的幾種級(jí)別。至于為什么會(huì)涉及到這一點(diǎn),可以這樣簡(jiǎn)單的理解:如果同一時(shí)刻,有兩個(gè)請(qǐng)求在執(zhí)行事務(wù)的操作,并且這兩個(gè)事務(wù)是對(duì)同一條數(shù)據(jù)做操作,那么到底最終的結(jié)果是以誰(shuí)的為準(zhǔn)呢?不同的隔離級(jí)別導(dǎo)致的結(jié)果不一樣,因此事務(wù)的隔離級(jí)別也是一個(gè)非常重要的點(diǎn).
隔離級(jí)別分為如下幾點(diǎn):
1. 未提交讀 (READ UNCOMMITTED)
一個(gè)事務(wù)中對(duì)數(shù)據(jù)所做的修改,即使沒(méi)有提交,這個(gè)修改對(duì)其他的事務(wù)仍是可見(jiàn)的,這種情況下就容易出現(xiàn)臟讀,影響了數(shù)據(jù)的完整性.
舉例:小明在用支付寶支付時(shí),查看了銀行卡的余額還有 300 塊,其實(shí)只有 100 塊,只是因?yàn)樗笥颜谙蜚y行卡存款了 200 塊,此時(shí)女朋友不想存了,點(diǎn)擊了回滾操作,小明進(jìn)行支付卻失敗了.
2. 讀提交 (READ COMMITTED)
一個(gè)事務(wù)開(kāi)始時(shí),只能看見(jiàn)其他已經(jīng)提交過(guò)的事務(wù)。這種情況下容易出現(xiàn)不可重復(fù)讀 (兩次讀的結(jié)果不一樣).
舉例:同樣用上面的例子舉例,當(dāng)他女朋友在刷卡時(shí)卡里余額有 100 塊,但是在點(diǎn)擊最終支付時(shí),提示余額不足,此時(shí)看卡里的錢(qián)沒(méi)了。這是因?yàn)樾∶髋笥言谥Ц稌r(shí),小明操作的事務(wù)還未提交,所以小明女朋友兩次看到的結(jié)果不一樣.
3. 可重復(fù)讀 (REPEATABLE READ)
多次讀取記錄的結(jié)果都是一致的,可重復(fù)讀可以解決上面的不可重復(fù)讀的情況。但是有這樣一種情況,當(dāng)一個(gè)事務(wù)在讀取某個(gè)范圍的記錄時(shí),另外一個(gè)事務(wù)在這個(gè)范圍內(nèi)插入了一條新的數(shù)據(jù),當(dāng)事務(wù)再次進(jìn)行讀取數(shù)據(jù)時(shí),發(fā)現(xiàn)比第一次讀取記錄多了一條,這就是所謂的幻讀,兩次讀取的結(jié)果不一致.
舉例:小明女朋友在查看銀行卡的記錄時(shí),看見(jiàn)有 5 條消費(fèi)記錄,此時(shí)小明正在消費(fèi),這時(shí)候消費(fèi)記錄里面記錄了這條消費(fèi)記錄,當(dāng)女朋友再次讀取記錄時(shí),發(fā)現(xiàn)有 6 條記錄了.
4. 可串行 (SERIALIZABLE)
串行就像一個(gè)隊(duì)列一個(gè)樣,每個(gè)事務(wù)都是排隊(duì)等候著執(zhí)行,只有前一個(gè)事務(wù)提交之后,下一個(gè)事務(wù)才能進(jìn)行操作。這種情況雖然可以解決上面的幻讀,但是他會(huì)在每一條數(shù)據(jù)上加一個(gè)鎖,容易導(dǎo)致大量的鎖超時(shí)和鎖競(jìng)爭(zhēng),特別不適用在一些高并發(fā)的業(yè)務(wù)場(chǎng)景下.
舉例:我們?cè)阢y行排隊(duì)存錢(qián),只有前一個(gè)人全部操作完,下一個(gè)人才可以進(jìn)行辦理。中間的人是不可以插隊(duì)的,只能一個(gè)一個(gè)的排對(duì),事務(wù)的串行就是這樣的一個(gè)概念,其實(shí)所謂的串行模式都是這樣的一個(gè)概念.


隔離性總結(jié)
通過(guò)上面的舉例,我們不難發(fā)現(xiàn)。臟讀和不可重復(fù)讀重在更新數(shù)據(jù),然后幻讀重在插入數(shù)據(jù).
多種存儲(chǔ)引擎時(shí)事務(wù)的處理方式
根據(jù)上面事務(wù)使用的條件,我們可以得知有的存儲(chǔ)引擎是不支持事務(wù)的,例如 MyISAM 存儲(chǔ)引擎就不支持。那如果在一個(gè)事務(wù)中使用了事務(wù)性的存儲(chǔ)引擎和非事務(wù)性的存儲(chǔ),提交是可以正常進(jìn)行,但是回滾非事務(wù)性的存儲(chǔ)引擎則會(huì)顯示響應(yīng)的錯(cuò)誤信息,具體信息和存儲(chǔ)引擎有關(guān).
如何使用事務(wù)
MySQL 中事務(wù)隱式開(kāi)啟的,也就是說(shuō),一個(gè) sql 語(yǔ)句就是一個(gè)事務(wù),當(dāng) sql 語(yǔ)句執(zhí)行完畢,事務(wù)就提交了。在演示的過(guò)程中,我們顯式開(kāi)啟.
MySQL 中的自動(dòng)提交
上面提到了 MySQL 中事務(wù)是隱式開(kāi)啟的,則代表我們每一個(gè) sql 是自動(dòng)提交的,需要關(guān)閉則需要設(shè)置 autocommit 選項(xiàng).
// 查看autocommit配置值(1或者ON則表示開(kāi)啟)
mysql [email protected]:(none)> show variables like '%autocommit%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set
Time: 0.018s
// 設(shè)置autocommit配置值
mysql [email protected]:(none)> set autocommit = 0;
Query OK, 0 rows affected
Time: 0.000s
mysql [email protected]:(none)> show variables like '%autocommit%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set
Time: 0.013s
1. 表結(jié)構(gòu)如下
mysql [email protected]:test> desc user;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | <null> | auto_increment |
| name | varchar(255) | YES | | <null> | |
| age | int(2) | YES | | <null> | |
+-------+--------------+------+-----+---------+----------------+
3 rows in set
Time: 0.013s
SQL 語(yǔ)句
CREATE TABLE `test`.`Untitled` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int(2) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
2. 使用事務(wù)
MySQL 實(shí)現(xiàn)事務(wù)
下面的代碼,我們主要做了如下幾個(gè)操作
a. 開(kāi)啟事務(wù)
b. 修改數(shù)據(jù)
c. 查詢數(shù)據(jù)是否改變
d. 數(shù)據(jù)回滾
e. 再次查詢數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)變回修改之前的狀態(tài)
f. 修改數(shù)據(jù)
g. 事務(wù)提交
h. 查詢數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)變?yōu)樽詈笠淮涡薷牡臓顟B(tài)
i. 嘗試事務(wù)回滾
j. 查詢驗(yàn)證是否被回滾了,發(fā)現(xiàn)數(shù)據(jù)還是為最后一次修改的狀態(tài),事務(wù)回滾失敗
// 我們先查看表中的數(shù)據(jù),id為1的age字段是12
mysql [email protected]:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 張三 | 12 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.013s
// 開(kāi)啟事務(wù)
mysql [email protected]:test> begin;
Query OK, 0 rows affected
Time: 0.001s
// 將id為1的age字段改為10
mysql [email protected]:test> update user set age=10 where id=1;
Query OK, 1 row affected
Time: 0.001s
// 再次查詢數(shù)據(jù)時(shí),發(fā)現(xiàn)數(shù)據(jù)改為修改后的值
mysql [email protected]:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 張三 | 10 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.012s
// 此時(shí)我們進(jìn)行回滾操作
mysql [email protected]:test> rollback;
Query OK, 0 rows affected
Time: 0.001s
// 再次查詢發(fā)現(xiàn)數(shù)據(jù)回到最初狀態(tài)
mysql [email protected]:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 張三 | 12 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.019s
// 我們?cè)俅螌?duì)數(shù)據(jù)進(jìn)行修改
mysql [email protected]:test> update user set age=15 where id=1;
Query OK, 1 row affected
Time: 0.001s
// 此時(shí)將事務(wù)進(jìn)行提交
mysql [email protected]:test> commit;
Query OK, 0 rows affected
Time: 0.000s
// 發(fā)現(xiàn)此時(shí)的數(shù)據(jù)變?yōu)槲覀冏罱K提交的值
mysql [email protected]:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 張三 | 15 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.012s
// 我們嘗試用剛才回滾的方式進(jìn)行還原數(shù)據(jù)
mysql [email protected]:test> rollback;
Query OK, 0 rows affected
Time: 0.000s
// 發(fā)現(xiàn)數(shù)據(jù)無(wú)法回退了,仍然是提交后的數(shù)據(jù)
mysql [email protected]:test> select * from user;
+----+------+-----+
| id | name | age |
+----+------+-----+
| 1 | 張三 | 15 |
| 2 | 李四 | 15 |
+----+------+-----+
2 rows in set
Time: 0.017s
php 實(shí)現(xiàn)事務(wù)實(shí)例代碼
<?php
// 連接MySQL
$mysqli = new mysqli('127.0.0.1', 'root', '123456', 'test', 3306);
// 關(guān)閉事務(wù)自動(dòng)提交
$mysqli->autocommit(false);
// 1.開(kāi)啟事務(wù)
$mysqli->begin_transaction();
// 2.修改數(shù)據(jù)
$mysqli->query("update user set age=10 where id=1");
// 3.查看數(shù)據(jù)
$mysqli->query("select * from user");
// 4.事務(wù)回滾
$mysqli->rollback();
// 5.查看數(shù)據(jù)
$mysqli->query("select * from user");
// 7.修改數(shù)據(jù)
$mysqli->query("update user set age=15 where id=1");
// 8.事務(wù)提交
$mysqli->commit();
// 9.事務(wù)回滾
$mysqli->rollback();
// 10.查看數(shù)據(jù)
$mysqli->query("select * from user");
如何設(shè)置事務(wù)的隔離級(jí)別
// 查看當(dāng)前的事務(wù)隔離級(jí)別
mysql [email protected]:test> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set
Time: 0.015s
// 設(shè)置隔離級(jí)別
set session transaction isolation level 隔離級(jí)別(上面事務(wù)隔離級(jí)別中的英文單詞);
以上就是MySQL 事務(wù)最全詳解的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注其它相關(guān)文章!