1.事務的作用
事務用于保證數據的一致性,它由一組相關的 dml (update delete insert) 語句組成,該組的 dml (update delete insert) 語句要么全部成功,要么全部失敗。
如:轉賬就要用事務來處理,用以保證數據的一致性。
假設張三給李四轉100塊:
銀行后臺有一張簡單的數據庫余額表:
為了完成轉賬這件事,銀行后臺需要執行這兩條sql語句:
這時候就會出現三種情況:
1.第一條語句執行失敗,第二條語句執行成功,結果出錯;
2.第一條語句執行成功,第二條語句執行失敗,結果出錯;
3.兩條語句全部執行失敗;
4.兩條語句全部執行成功;
假如你是銀行的話,當然會希望這兩條語句要么同時失敗,要么同時成功。
這就引出我們的需求:兩個sql語句作為一個整體,用以保證數據的一致性。
當執行事務操作時 (dml (update delete insert) 語句) ,mysql 會在表上加鎖,防止其它用戶改表的數據。這對用戶來講是非常重要的。
2.事務的操作
2.1開始一個事務
基本語法:
start transaction; -- 開始一個事務
2.2設置保存點
保存點 (savepoint), 保存點是事務中的點,用于取消部分事務,當結束事務時(commit),會自動的刪除該事務所定義的所有保存點。
當執行回退事務時,通過指定保存點可以回退到指定的點。
基本語法:
savepoint 保存點名; -- 設置保存點
2.3回退事務
基本語法:
rollback to 保存點名; -- 回退事務
2.4回退全部事務
基本語法:
rollback; -- 回退全部事務
2.5提交事務
使用 commit 語句可以提交事務。當執行了 commit 語句子后,會確認事務的變化、結束事務、刪除保存點、釋放鎖,數據生效。
當使用 commit 語句結束事務子后,其它會話【其他用戶的連接】將可以查看到事務變化后的新數據。
類似于下圖。而讓用戶2在用戶1開始事務時對用戶1的操作的數據不可見的功能就叫做隔離。
基本語法:
commit; -- 提交事務,所有的操作生效,不能回退
2.6示例
先創建一個簡單的表:
CREATE TABLE t03 (id INT,`name` VARCHAR(32)
);
開啟一個事務:
START TRANSACTION;
插入一條數據:
insert into t03 values(1,'tom');
設置一個保存點:
savepoint a;
再次插入一條數據:
insert into t03 values(2,'Jack');
再設置一個保存點:
savepoint b;
再次插入一條數據:
insert into t03 values(3,'lucy');
回滾到保存點a:
ROLLBACK TO a;
回滾全部:回到了最開始剛開啟事務時的樣子。
ROLLBACK;
再次插入一條數據:
insert into t01 values(4,'jep');
提交事務:
COMMIT;
再次回滾全部:
ROLLBACK;
數據已經生效,保存點全部被刪除,回滾不了了。
3.事務的使用細節
3.1 沒有設置保存點
如果不開始事務,默認情況下,dml 操作是自動提交的,不能回滾。
如果開始一個事務,你沒有創建保存點。你可以執行 rollback,默認就是回退到你事務開始的狀態.
3.2 多個保存點
你也可以在這個事務中 (還沒有提交時), 創建多個保存點。
比如:
savepoint aaa;
執行 dml;
savepoint bbb;
你可以在事務沒有提交前,選擇回退到哪個保存點。
3.3 存儲引擎
mysql 的事務機制需要 innodb 的存儲引擎才可以使用,myisam 不好使。
3.4 開始事務方式
開始一個事務有兩種方式:
start transaction;
set autocommit=off;
4.四種隔離級別
4.1隔離的基本介紹
事務隔離級別介紹:
- 多個連接開啟各自事務操作數據庫中數據時,數據庫系統要負責隔離操作,以保證各個連接在獲取數據時的準確性。(通俗解釋)
- 如果不考慮隔離性,可能會引發如下問題:
? 臟讀
? 不可重復讀
? 幻讀
舉個例子:當MySQL同時又兩個連接C1,C2
C1在對這個表進行dml操作的時候,C2再查詢這張表的時候所看到的是怎樣的一種數據,取決于C2的隔離級別。
4.2不做隔離引發的問題
①臟讀(dirty read):
當一個事務讀取另一個事務尚未提交的修改時,產生臟讀。
②不可重復讀(nonrepeatable read):
同一查詢在同一事務中多次進行,由于其他提交事務所做的修改或刪除,每次返回不同的結果集,此時發生不可重復讀。
③幻讀(phantom read):
同一查詢在同一事務中多次進行,由于其他提交事務所做的插入操作,每次返回不同的結果集,此時發生幻讀。
4.3事務隔離級別
概念:Mysql隔離級別定義了事務與事務之間的隔離程度。
Mysql隔離級別(4種) | 臟讀 | 不可重復讀 | 幻讀 | 加鎖讀 | 加鎖寫 |
讀未提交(Read uncommitted) | V | V | V | 不加鎖 | 加鎖(排他鎖) |
讀已提交(Read committed) | x | V | V | 不加鎖 | 加鎖(排他鎖) |
可重復讀(Repeatable read) | x | x | x | 不加鎖 | 加鎖(排他鎖 + 間隙鎖 / 臨鍵鎖) |
可串行化(Serializable) | x | x | x | 加鎖 | 加鎖(排他鎖) |
說明:V 可能出現 ×不會出現
4.4事務隔離級別的特性
mysql事務ACID
- 事務的acid特性
- 原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。 - 一致性(Consistency)
事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。 - 隔離性(Isolation)
事務的隔離性是多個用戶并發訪問數據庫時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作數據所干擾,多個并發事務之間要相互隔離。 - 持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
- 原子性(Atomicity)
4.5事務隔離級別的設置與查看
mysql事務隔離級別
- 查看當前會話隔離級別(用戶臨時登錄時的隔離級別)
select @@tx_isolation;
- 查看系統當前隔離級別(所有用戶登錄時默認的隔離級別)
select @@global.tx_isolation;
- 設置當前會話隔離級別
set session transaction isolation level [Read uncommitted/Read committed/repeatable read/Serializable];
- 設置系統當前隔離級別
set global transaction isolation level [Read uncommitted/Read committed/repeatable read/Serializable];
- mysql 默認的事務隔離級別是 repeatable read ,一般情況下,沒有特殊要求,沒有必要修改(因為該級別可以滿足絕大部分項目需求)
真的想改mysql系統默認隔離級別的話:
全局修改,修改 mysql.ini 配置文件,在最后加上transaction-isolation = REPEATABLE-READ
--可選參數有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.
之后重啟mysql服務,就完成了修改。
4.6事務隔離級別的演示
①讀未提交
左右都開一個mysql的連接,左邊我們取做連接1,右邊取做連接2;
選取一個數據庫:
use mysql_learn;
在該數據庫下創建一個表:
create table account (id int,name varchar(32),money int);
查看一下兩邊的事務隔離級別:
select @@tx_isolation;
連接1的為REPEATABLE-READ(可重復讀),也就是mysql默認的事務隔離級別;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
連接2的設置為讀未提交:
set session transaction isolation level Read uncommitted;
再次查看一下連接2的隔離級別為READ-UNCOMMITTED(讀未提交):
select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
現在我們在兩個連接上同時開啟事務:
連接2的事務中查看一下該表:
我們在連接1執行幾條dml語句:
insert into account values(100,'tom',3000);
insert into account values(200,'jack',5000);
update account set money = money + 100 where id = 100;
注意!我們連接1和連接2的事務都是沒有提交的,但是此刻在連接2查看一下這張表:
連接1提交一下事務,再在連接2查看一下這張表:
在并行的連接1和連接2中的事務中,我們會發現,在連接2中還未提交的事務中多次查看account表的結果會由于連接1中的事務中的dml操作以及事務的提交而變化。
這也就是“讀未提交”這個隔離級別可能會造成贓讀,不可重復讀,幻讀等問題。
②讀已提交
將連接2的事務也提交一下(其實什么都沒做,只是演示);
現在將連接2的隔離級別設置為:Read committed(讀已提交);
set session transaction isolation level Read committed;
現在,將連接1和連接2中同時開啟事務:
連接2的事務中查看一下該表:
在連接1的事務中執行幾句dml:
insert into account values(300,'lucy',9999);
insert into account values(400,'chen',10000);
update account set money = money + 10000 where id = 300;
注意!此刻連接1和2的事務都是沒有提交的,我們在連接2的事務中查看一下該表:
可以發現,連接1未提交的事務中做的修改,沒有在連接2的事務中查看到;
將連接1的事務提交一下,再在沒有提交的連接2開啟的事務中查看該表:
在并行的連接1和連接2中的事務中,可以發現在沒有提交的連接2開啟的事務當中,對account表的查看結果受到了連接1中的含有dml操作的事務的提交而改變。
在“讀已提交”的隔離級別下,可能會引起不可重復讀以及幻讀問題。
③可重復讀
將連接2的事務提交一下,并且將其隔離級別設置為Repeatable read(可重復讀):
commit;
set session transaction isolation level repeatable read;
連接1和連接2再次開啟事務:
連接2的事務中查看一下該表:
在連接1的事務中執行幾句dml:
insert into account values(500,'kimi',8888);
insert into account values(600,'liu',20000);
update account set money = money - 1000 where id = 500;
注意!此刻連接1和2的事務都是沒有提交的,我們在連接2的事務中查看一下該表:
可以發現,連接1未提交的事務中做的修改,沒有在連接2的事務中查看到;
將連接1的事務提交一下,再在沒有提交的連接2開啟的事務中查看該表:
在并行的連接1和連接2中的事務中,可以發現在沒有提交的連接2開啟的事務當中對account表的多次查看結果并沒有受到已經提交了的連接1開啟的事務的dml操作以及提交而改變。
也就是說可重復讀這個隔離級別不會有贓讀,不可重復讀以及幻讀等問題。
④可串行化
將連接2的事務提交一下,并且將其隔離級別設置為:Serializable(可串行化);
commit;
set session transaction isolation level Serializable;
連接1和連接2再次開啟事務:
連接2的事務中查看一下該表:
在連接1的事務中執行幾句dml:
insert into account values(700,'chuyi',22222);
insert into account values(800,'shiwu',33333);
update account set money = money + 11111 where id = 700;
注意!此刻連接1和2的事務都是沒有提交的,我們在連接2的事務中查看一下該表:
會發現回車鍵輸入無效,連接2的事務中查看不了正在被連接1事務操作的account這張表!
這就說明account這張表對于隔離級別為“可串行化”的連接2開啟的事務來說是被上了鎖的。
我們將連接1的事務提交一下:
再在沒有提交的連接2開啟的事務中查看該表:
在實際上并行的連接1和連接2中的事務中,由于可串行化的隔離級別的加鎖機制,使得連接1和連接2中的事務得到了形式上串行的效果,并且也達到了數據同步得效果。