一、事務(wù)簡介
1、在 MySQL 中只有使用了 Innodb 數(shù)據(jù)庫引擎的數(shù)據(jù)庫或表才支持事務(wù)疯特。
2、事務(wù)處理可以用來維護數(shù)據(jù)庫的完整性肛走,保證成批的 SQL 語句要么全部執(zhí)行漓雅,要么全部不執(zhí)行。
3朽色、事務(wù)只和DML語句有關(guān)邻吞,或者說DML語句才有事務(wù)。DML:Data Manipulation Language的縮寫葫男,意思是數(shù)據(jù)操縱語言,也就是INSERT抱冷、UPDATE、DELETE梢褐。
二旺遮、事務(wù)四大特征(ACID)
一般來說,事務(wù)是必須滿足4個條件(ACID)::原子性(Atomicity盈咳,或稱不可分割性)耿眉、一致性(Consistency)、隔離性(Isolation鱼响,又稱獨立性)跷敬、持久性(Durability)。
原子性:一個事務(wù)(transaction)中的所有操作热押,要么全部完成,要么全部不完成斤寇,不會結(jié)束在中間某個環(huán)節(jié)桶癣。事務(wù)在執(zhí)行過程中發(fā)生錯誤,會被回滾(Rollback)到事務(wù)開始前的狀態(tài)娘锁,就像這個事務(wù)從來沒有執(zhí)行過一樣牙寞。
一致性:在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預(yù)設(shè)規(guī)則间雀,這包含資料的精確度悔详、串聯(lián)性以及后續(xù)數(shù)據(jù)庫可以自發(fā)性地完成預(yù)定的工作。
隔離性:數(shù)據(jù)庫允許多個并發(fā)事務(wù)同時對其數(shù)據(jù)進行讀寫和修改的能力惹挟,隔離性可以防止多個事務(wù)并發(fā)執(zhí)行時由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致茄螃。事務(wù)隔離分為不同級別,包括讀未提交(Read uncommitted)连锯、讀提交(read committed)归苍、可重復(fù)讀(repeatable read)和串行化(Serializable)。
持久性:事務(wù)處理結(jié)束后运怖,對數(shù)據(jù)的修改就是永久的拼弃,即便系統(tǒng)故障也不會丟失。
在 MySQL 命令行的默認設(shè)置下摇展,事務(wù)都是自動提交的吻氧,即執(zhí)行 SQL 語句后就會馬上執(zhí)行 COMMIT 操作。因此要顯式地開啟一個事務(wù)咏连,須使用命令 BEGIN 或 START TRANSACTION盯孙,或者執(zhí)行命令 SET AUTOCOMMIT=0,用來禁止使用當前會話的自動提交捻勉。
三镀梭、事務(wù)控制語句:
事務(wù)其實可以劃分為兩大類:隱式的事務(wù)和顯示的事務(wù)
隱式的事務(wù)很簡單,比如我們的insert踱启、delete报账、update這些語句都是隱式的事務(wù)。
顯示的事務(wù)指的是帶有很明顯的開始和結(jié)束的標記
BEGIN 或 START TRANSACTION 顯式地開啟一個事務(wù)埠偿;
COMMIT 會提交事務(wù)透罢,并使已對數(shù)據(jù)庫進行的所有修改成為永久性的;
ROLLBACK 回滾會結(jié)束用戶的事務(wù)冠蒋,并撤銷正在進行的所有未提交的修改羽圃;
SAVEPOINT 保存點名稱,SAVEPOINT允許在事務(wù)中創(chuàng)建一個保存點抖剿,一個事務(wù)中可以有多個 SAVEPOINT朽寞;
RELEASE SAVEPOINT identifier 刪除一個事務(wù)的保存點,當沒有指定的保存點時斩郎,執(zhí)行該語句會拋出一個異常脑融;
ROLLBACK TO 保存點名稱 把事務(wù)回滾到標記點;
set [ global | session ] transaction isolation level 用來設(shè)置事務(wù)的隔離級別缩宜。InnoDB 存儲引擎提供事務(wù)的隔離級別有READ UNCOMMITTED(讀未提交)肘迎、READ COMMITTED(讀已提交)甥温、REPEATABLE READ (可重復(fù)讀)和 SERIALIZABLE(串行)。
如果選擇global妓布,意思是此語句將應(yīng)用于之后的所有session姻蚓,而當前已經(jīng)存在的session不受影響。
如果選擇session匣沼,意思是此語句將應(yīng)用于當前session內(nèi)之后的所有事務(wù)狰挡。
創(chuàng)建表
CREATE TABLE `salary` (
`id` int(11) NOT NULL,
`name` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`money` decimal(11, 2) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
查看當前事務(wù)是否開啟:
show variables like '%autocommit%';
值為 0 和值為 OFF:關(guān)閉事務(wù)自動提交。
值為 1 和值為 ON:開啟事務(wù)自動提交肛著。
我們這就不關(guān)閉了圆兵,因為都是使用begin顯式的開啟事務(wù)。
當然也可以修改事務(wù)是否開啟
set autocommit = 1 ; 0或者1
查看當前事務(wù)級別:
1.查看當前會話隔離級別
select @@tx_isolation;
2.查看系統(tǒng)當前隔離級別
select @@global.tx_isolation;
當前為可重復(fù)讀枢贿。也是mysql的默認級別殉农。
commit示例:
mysql> begin; #顯示的開啟事務(wù)
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO salary VALUES(1,'張三',2000);
Query OK, 1 row affected (0.00 sec)
mysql> select * from salary; //在當前事務(wù)是可以查詢數(shù)據(jù)的
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
+----+------+---------+
1 row in set (0.02 sec)
這時重新打開一個 cmd 窗口,查看 salary 數(shù)據(jù)表
mysql> select * from salary;
Empty set
可以看到數(shù)據(jù)是空的局荚。
下面在之前的窗口中使用 COMMIT 語句提交事務(wù)超凳,如下所示:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
然后再次在第2個窗口執(zhí)行查詢命令,之后都叫2窗口了耀态。
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
+----+------+---------+
1 row in set (0.01 sec)
在本例中轮傍,關(guān)閉自動提交后,該位置會作為一個事務(wù)起點首装,直到執(zhí)行 COMMIT 語句和 ROLLBACK 語句后创夜,該事務(wù)才結(jié)束。
ROLLBACK示例:
在1窗口繼續(xù)執(zhí)行insert語句:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO salary VALUES(2,'李四',5000);
Query OK, 1 row affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 2 | 李四 | 5000.00 |
+----+------+---------+
2 rows in set (0.03 sec)
2窗口:
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
+----+------+---------+
1 row in set (0.02 sec)
數(shù)據(jù)還是一條仙逻。
1窗口:
mysql> rollback; #執(zhí)行回滾
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
+----+------+---------+
1 row in set (0.02 sec)
數(shù)據(jù)變?yōu)榱艘粭l驰吓。
當我們在一個事務(wù)中執(zhí)行了多次insert或者update語句之后,然后想執(zhí)行回滾系奉,發(fā)現(xiàn)它回到了最開始的數(shù)據(jù)檬贰,那能不能指定回到那個事務(wù)呢?答案是可以的缺亮,事務(wù)提供了事務(wù)保存點翁涤。
SAVEPOINT示例:
下面演示將向表salary中連續(xù)插入3條數(shù)據(jù),在插入第2條數(shù)據(jù)的后面定義一個保存點萌踱,最后看看能否回滾到此保存點葵礼。
1窗口:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO salary VALUES(3,'王五',5000);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO salary VALUES(4,'趙六',5000);
Query OK, 1 row affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.02 sec)
定義保存點
mysql> savepoint s4;
Query OK, 0 rows affected (0.00 sec)
保存第三條數(shù)據(jù)
mysql> INSERT INTO salary VALUES(5,'田七',6000);
Query OK, 1 row affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
| 5 | 田七 | 6000.00 |
+----+------+---------+
4 rows in set (0.03 sec)
回滾到保存點s4
mysql> rollback to savepoint s4; #回滾到保存點
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
可以看到在保存點之后的數(shù)據(jù)就消失了,保存點之前的數(shù)據(jù)就保存了并鸵。
2窗口:
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
+----+------+---------+
1 row in set (0.02 sec)
在二窗口我們可以看到章咧,竟然沒有最新數(shù)據(jù),這是為什么呢能真?因為事務(wù)回滾到指定保存點并不代表事務(wù)結(jié)束了,需要執(zhí)行commit或者rollback.
1窗口:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
2窗口
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
然后如果保存點不想要了,可以撤銷保存點
release savepoint 保存點名 --撤銷保存點
四粉铐、事務(wù)的隔離級別
隔離性有隔離級別(4個)
讀未提交:read uncommitted
讀已提交:read committed
可重復(fù)讀:repeatable read
串行化:serializable
4.1 讀未提交(read uncommitted)
事務(wù)A和事務(wù)B疼约,事物A未提交的數(shù)據(jù),事物B可以讀取到
這里讀取到的數(shù)據(jù)叫做“臟數(shù)據(jù)”
這種隔離級別最低蝙泼,這種級別一般是在理論上存在程剥,數(shù)據(jù)庫隔離級別一般都高于該級別。
修改全局隔離級別為讀未提交
mysql> select @@global.tx_isolation; #查看系統(tǒng)當前隔離級別
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.02 sec)
mysql> set global transaction isolation level read uncommitted; #修改事務(wù)級別為讀未提交
Query OK, 0 rows affected (0.00 sec)
mysql> commit
-> ;
Query OK, 0 rows affected (0.02 sec)
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set (0.02 sec)
不知道為什么執(zhí)行SELECT @@session.tx_isolation;查看session的事務(wù)等級還是REPEATABLE-READ汤踏,并沒有變?yōu)镽EAD-UNCOMMITTED织鲸,只有當我關(guān)閉navicat工具之后,重新打開命令行查詢才會變?yōu)镽EAD-UNCOMMITTED溪胶。
示例:
窗口1
mysql> begin; #開啟事務(wù)
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.02 sec)
mysql> update salary set money = money + 5000 where name = '張三'; #張三的薪資增加了5000
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
窗口2
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.02 sec)
可以看到窗口1都沒有進行提交搂擦,窗口2就已經(jīng)查詢出來了。
窗口1進行回滾
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
窗口2
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
出現(xiàn)上述情況哗脖,即我們所說的臟讀 瀑踢,兩個并發(fā)的事務(wù),“事務(wù)A:領(lǐng)導(dǎo)給張三發(fā)工資”才避、“事務(wù)B:張三查詢工資賬戶”橱夭,事務(wù)B讀取了事務(wù)A尚未提交的數(shù)據(jù)。
4.2 讀已提交(read committed)
事務(wù)A和事務(wù)B桑逝,事務(wù)A已經(jīng)提交的數(shù)據(jù)棘劣,事務(wù)B才能讀取到
這種隔離級別高于讀未提交,這種級別可以避免臟讀
但是這種隔離級別會導(dǎo)致“不可重復(fù)讀”楞遏。
示例:
mysql> set global transaction isolation level read committed; #隔離級別修改為讀已提交
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set (0.03 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
窗口1顯示開啟事務(wù)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update salary set money = money + 5000 where name = '張三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.04 sec)
窗口2
mysql> SELECT * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
窗口2張三的余額還是2000,這已經(jīng)避免了臟讀茬暇。
窗口1進行提交操作
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
窗口2
mysql> SELECT * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 2000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
mysql> SELECT * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
這樣就會發(fā)現(xiàn)前后兩次查詢結(jié)果不一致。就出現(xiàn)了不可重復(fù)讀橱健。
不可重復(fù)讀是指在一個事務(wù)內(nèi)而钞,多次讀同一數(shù)據(jù)。在這個事務(wù)還沒有結(jié)束時拘荡,另外一個事務(wù)也訪問該同一數(shù)據(jù)臼节。那么,在第一個事務(wù)中的兩次讀數(shù)據(jù)之間珊皿,由于第二個事務(wù)的修改网缝,那么第一個事務(wù)兩次讀到的的數(shù)據(jù)可能是不一樣的。這樣就發(fā)生了在一個事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的蟋定,因此稱為是不可重復(fù)讀粉臊。(即不能讀到相同的數(shù)據(jù)內(nèi)容)
在讀已提交(read committed)這種隔離級別下還會出現(xiàn)幻讀
,那也演示一下
示例:
窗口2查詢id大于的數(shù)據(jù)為兩條
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary where id > 1;
+----+------+----------+
| id | name | money |
+----+------+----------+
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 20000.00 |
+----+------+----------+
2 rows in set (0.03 sec)
窗口1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into salary values(2,'二狗',5000);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
窗口2
mysql> select * from salary where id > 1;
+----+------+----------+
| id | name | money |
+----+------+----------+
| 2 | 二狗 | 5000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 20000.00 |
+----+------+----------+
3 rows in set (0.03 sec)
前后兩次的數(shù)據(jù)量不一致驶兜,這就產(chǎn)生了幻讀扼仲。
4.3可重復(fù)讀(repeatable read)
事務(wù)A和事務(wù)B远寸,事務(wù)A提交之后的數(shù)據(jù),事務(wù)B讀取不到屠凶,事務(wù)B是可重復(fù)讀取數(shù)據(jù)驰后,這種隔離級別高于讀已提交〈@ⅲ可重復(fù)讀是MySQL的默認級別灶芝。
示例
mysql> set global transaction isolation level repeatable read; #修改事務(wù)隔離級別為可重復(fù)讀
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.02 sec)
窗口1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.03 sec)
mysql> update salary set money = money + 15000 where name = '趙六';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
窗口2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 |
+----+------+---------+
3 rows in set (0.02 sec)
窗口1進行提交
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from salary;
+----+------+----------+
| id | name | money |
+----+------+----------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 20000.00 |
+----+------+----------+
3 rows in set (0.03 sec)
窗口2
mysql> select * from salary;
+----+------+---------+
| id | name | money |
+----+------+---------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 5000.00 | #數(shù)據(jù)還是5000
+----+------+---------+
3 rows in set (0.03 sec)
這樣可以看出不可重復(fù)讀就避免了。事務(wù)A進行提交了之后唉韭,事務(wù)B查詢的數(shù)據(jù)還是第一次查詢的數(shù)據(jù)夜涕。
再開一個命令行窗口,也就代表新開了一個事務(wù)
窗口3
mysql> select * from salary;
+----+------+----------+
| id | name | money |
+----+------+----------+
| 1 | 張三 | 7000.00 |
| 3 | 王五 | 5000.00 |
| 4 | 趙六 | 20000.00 |
+----+------+----------+
3 rows in set (0.01 sec)
如果窗口1進行新增數(shù)據(jù)属愤,然后進行提交女器,窗口2還是查詢不出來的,在mysql的可重復(fù)讀這種隔離級別下是避免了幻讀的春塌。
4.4 Serializable 序列化
Serializable 是最高的事務(wù)隔離級別晓避,同時代價也花費最高,性能很低只壳,一般很少使用俏拱,在該級別下,事務(wù)順序執(zhí)行吼句,不僅可以避免臟讀锅必、不可重復(fù)讀,還避免了幻像讀惕艳。
下一篇就介紹讀已提交和可重復(fù)讀是怎么實現(xiàn)的搞隐?