abstrak:簡介事務(wù)是一組原子性的 SQL 查詢, 或者說是一個獨立的工作單元. 在事務(wù)內(nèi)的語句, 要么全部執(zhí)行成功, 要么全部執(zhí)行失敗.事務(wù)的 ACID 性質(zhì)數(shù)據(jù)庫事務(wù)擁有以下四個特性, 即 ACID 性質(zhì):原子性(Atomicity): 事務(wù)作為一個整體被執(zhí)行, 包含在其中的對數(shù)據(jù)庫的操作要么全部執(zhí)行成功, 要么全部失敗回滾. 對于一個事務(wù)來說, 不可能只執(zhí)行其中一部分操作, 這就是事務(wù)的原子性.一致性(
簡介
事務(wù)是一組原子性的 SQL 查詢, 或者說是一個獨立的工作單元. 在事務(wù)內(nèi)的語句, 要么全部執(zhí)行成功, 要么全部執(zhí)行失敗.
事務(wù)的 ACID 性質(zhì)
數(shù)據(jù)庫事務(wù)擁有以下四個特性, 即 ACID 性質(zhì):
原子性(Atomicity): 事務(wù)作為一個整體被執(zhí)行, 包含在其中的對數(shù)據(jù)庫的操作要么全部執(zhí)行成功, 要么全部失敗回滾. 對于一個事務(wù)來說, 不可能只執(zhí)行其中一部分操作, 這就是事務(wù)的原子性.
一致性(Consistency): 事務(wù)應(yīng)確保數(shù)據(jù)庫的狀態(tài)從一個一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€一致狀態(tài).
隔離性(Isolation): 多個事務(wù)并發(fā)執(zhí)行時, 一個事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行.
持久性(Durability): 已被提交的事務(wù)對數(shù)據(jù)庫的修改應(yīng)該永久保存在數(shù)據(jù)庫中.
事務(wù)的隔離級別
SQL 中定義了四種隔離級別, 每種級別都規(guī)定了一個事務(wù)所做的修改,哪些在事務(wù)內(nèi)和事務(wù)間是可見的, 哪些是不可見的. 較低的隔離通??梢詧?zhí)行更高的并發(fā), 系統(tǒng)的開銷也更低.
SQL 標(biāo)準(zhǔn)中的四種隔離有:
READ UNCOMMITED(未提交讀)
在 READ UNCOMMITED 級別中, 事務(wù)的修改, 即使沒有提交, 對其他事務(wù)也是可見的. 其他事務(wù)可以讀取此事務(wù)中的未提交的數(shù)據(jù), 這也被稱為臟讀(Dirty Read). 此事務(wù)隔離級別會導(dǎo)致很多問題, 并且性能也不會比其他事務(wù)隔離級別好多少, 因此在實際環(huán)境中很少使用.
READ COMMITED(提交讀)
大多數(shù)的數(shù)據(jù)庫默認(rèn)隔離級別都是 READ COMMITED, 但是 MySQL 并不是. 在 READ COMMITED 級別中, 一個事務(wù)從開始到提交之前, 所做的任何修改對其他事務(wù)都是不可見的.
這個級別有時候也叫做不可重復(fù)讀(nonrepeatable read), 因為兩次執(zhí)行相同的查詢, 可能會得到不一樣的結(jié)果.
REPEATABLE READ(可重復(fù)讀)
REPEATABLE READ 解決了臟讀的問題. 該級別保證了在同一個事務(wù)中多次讀取同樣記錄的結(jié)果時一致的. 但是理論上, 可重復(fù)讀隔離級別還是無法解決另一個幻讀(Phantom Read)的問題. 所謂幻讀, 指的是當(dāng)某個事務(wù)在讀取某個范圍內(nèi)的記錄時, 另外一個事務(wù)又在該范圍內(nèi)插入了新的記錄, 當(dāng)之前的事務(wù)再次讀取該范圍的記錄時, 會產(chǎn)生幻行(Phantom Row).
可重復(fù)讀是 MySQL 的默認(rèn)事務(wù)隔離級別.
SERIALIZABLE(可串行化)
SERIALIZABLE 是最高的隔離級別. 他通過強制事務(wù)串行執(zhí)行, 避免了前面說的幻讀的問題. 簡單來說, SERIALIZABLE 會在讀取的每一行數(shù)據(jù)上都加上鎖, 所以可能導(dǎo)致大量的超時和鎖爭用的問題. 實際應(yīng)用中也很少用到這個隔離級別, 只有在非常需要確保數(shù)據(jù)的一致性而且可以接受沒有并發(fā)的情況下, 才考慮采用該級別.
死鎖
死鎖是指兩個或多個事務(wù)在同一個資源上相互占用, 并請求鎖定對方占用的資源, 從而導(dǎo)致惡性循環(huán)的現(xiàn)象. 當(dāng)多個事務(wù)試圖以不同順序鎖定資源時, 就可能產(chǎn)生死鎖.
死鎖發(fā)生以后, 只有部分或者完全回滾其中一個事務(wù), 才能打破死鎖.
MySQL 中的事務(wù)
在 MySQL 提供的眾多存儲引擎中, 只有 InnoDB 和 NDB Cluster 支持事務(wù).
關(guān)于自動提交(AUTOCOMMIT)
MySQL 默認(rèn)采用自動提交(AUTOCOMMIT) 模式. 即如果不顯示地開始一個事務(wù), 則每個操作都被當(dāng)做一個事務(wù)執(zhí)行提交操作.
我們可以通過
SHOW VARIABLES LIKE 'autocommit';
查詢當(dāng)前是否已經(jīng)開啟了字段提交事務(wù), 例如:
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
如果是 ON, 則表示已經(jīng)開啟了, 0 或 OFF 表示禁用.
可以通過 "set autocommit=0;" 來禁用自動提交:
set autocommit=0;
對于非事務(wù)型存儲引擎, 例如 MyISAM, 修改 AUTOCOMMIT 屬性試不會有影響的.
自動提交和非自動提交的區(qū)別
下面以一個例子來展示 autocommit 啟動和非啟動時的區(qū)別.
首先建立一個測試用的表:
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT '',
`age` INT(11) DEFAULT '0',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
接著關(guān)閉自動提交功能:
mysql> set autocommit=0;
然后插入一個數(shù)據(jù):
mysql> INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
接著查看數(shù)據(jù):
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | xys | 18 |
+----+------+------+
1 row in set (0.00 sec)
數(shù)據(jù)庫中可以查詢到這條數(shù)據(jù)了.
但是我們通過 SHOW BINLOG EVENTS 查看操作日志:
mysql> show binlog events;
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.13-log, Binlog ver: 4 |
| mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
2 rows in set (0.00 sec)
會發(fā)現(xiàn)上面的輸出中, 并沒有插入數(shù)據(jù)相關(guān)的記錄, 并且此時如果我們退出 MySQL 的話, 那么我們插入的數(shù)據(jù)并沒有被保存:
mysql> exit
Bye
>>> mysql -u root -p
Enter password:
mysql> use test;
Database changed
mysql> select * from user;
Empty set (0.00 sec)
mysql>
如果我們不退出, 而是輸入COMMIT; 時, 那么此時數(shù)據(jù)才真正保存到 MySQL 中:
mysql> commit;
Query OK, 0 rows affected (0.02 sec)
mysql> show binlog events;
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.13-log, Binlog ver: 4 |
| mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | |
| mysql-bin.000001 | 154 | Anonymous_Gtid | 1 | 219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 219 | Query | 1 | 291 | BEGIN |
| mysql-bin.000001 | 291 | Table_map | 1 | 342 | table_id: 108 (test.user) |
| mysql-bin.000001 | 342 | Write_rows | 1 | 394 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000001 | 394 | Xid | 1 | 425 | COMMIT /* xid=58 */ |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
7 rows in set (0.00 sec)
從上面的操作中我們可以看到, 當(dāng)禁用了 AUTOCOMMIT 后, 我們對數(shù)據(jù)庫的寫入操作并不會實際落地到數(shù)據(jù)庫中, 除非我們顯示地提交事務(wù).
接下來, 我們使能 AUTOCOMMIT, 再次進行相同的操作.
mysql> set autocommit=1;
mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
然后插入一個數(shù)據(jù):
mysql> INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
接著查看數(shù)據(jù):
mysql> select * from user;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | xys | 18 |
+----+------+------+
1 row in set (0.00 sec)
數(shù)據(jù)庫中可以查詢到這條數(shù)據(jù)了.
我們再次查看 binlog, 對比一下和禁用 AUTOCOMMIT 時有什么差別:
mysql> show binlog events;
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.13-log, Binlog ver: 4 |
| mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | |
| mysql-bin.000001 | 154 | Anonymous_Gtid | 1 | 219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
| mysql-bin.000001 | 219 | Query | 1 | 291 | BEGIN |
| mysql-bin.000001 | 291 | Table_map | 1 | 342 | table_id: 108 (test.user) |
| mysql-bin.000001 | 342 | Write_rows | 1 | 394 | table_id: 108 flags: STMT_END_F |
| mysql-bin.000001 | 394 | Xid | 1 | 425 | COMMIT /* xid=292 */ |
+------------------+-----+----------------+-----------+-------------+---------------------------------------+
7 rows in set (0.00 sec)
我們看到, 和禁用 AUTOCOMMIT 不同的是, 使能 AUTOCOMMIT 時, 每個寫操作都會進行事務(wù)的提交. 即上面的 insert 操作等效為:
BEGIN;
INSERT INTO user (`id`, `name`, `age`) VALUES (1, 'xys', 18);
COMMIT;