關于事務
我們在資料庫中需要執行一個系列的操作的時候,要保證這個系列執行的連續性和完整性,要麼整個系列的執行都成功,要麼就全部失敗(隻要有一個步驟失敗,其他均復原到之前的狀态),
保證不會存在一部分成功一部分失敗的情況。這就是我們事務的職責。下面舉個分蘋果的例子:
A同學有3個蘋果,B同學有2個蘋果,如果A同學給一個蘋果給B同學,那麼A同學隻剩下2個蘋果,而B同學有了3個。步驟如下
1 update tname set apples=apples-1 where name = "A";
2 update tname set apples=apples+1 where name = "B";
當然,這是理想情況下的結果。有可能會出錯:A同學減去了一個蘋果,然後執行B同學的加蘋果的時候,系統宕掉了,B同學沒有加成功,A同學的庫存中無厘頭少了一個蘋果。
這時候我們就需要事務支援了,有事務的情況下,就有兩種狀态,要麼都成功,要麼都失敗。
操作成功:A同學減去一個蘋果,B同學增加一個蘋果,最終A 2個,B 3個。
操作失敗:A同學依舊是3個蘋果,B同學依舊是2個蘋果。
事務有如下特性(參考官方描述):
1、在 MySQL 中隻有使用了 Innodb 資料庫引擎的資料庫或表才支援事務。
2、事務處理可以用來維護資料庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行。
3、事務用來管理 insert,update,delete 語句。
事務的四個特性(ACID)
一般來說,衡量事務必須滿足四個特性:ACID,即 原子性(Atomicity,或稱不可分割性)、一緻性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。
原子性(Atomicity):一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被復原(Rollback)到事務開始前的狀态,就像這個事務從來沒有執行過一樣。
一緻性(Consistency):在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精确度、串聯性以及後續資料庫可以自發性地完成預定的工作。
隔離性(Isolation):資料庫允許多個并發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導緻資料的不一緻。事務隔離分為不同級别,包括讀未送出(read uncommitted)、讀送出(read committed)、可重複讀(repeatable read)和串行化(Serializable),下面會詳細說明。
持久性(Durability):事務處理結束後,對資料的修改就是永久的,會持久化到硬碟上,即便系統故障也不會丢失。
顯示和隐式事務
事務分為顯示事務和隐式事務,
隐式事務:事務自動開啟、送出或復原,比如insert、update、delete語句,事務的開啟、送出或復原由mysql内部自動控制的
顯示事務:事務需要手動開啟、送出或復原,由開發者自己控制。
自動送出
MySQL中事務預設是隐式事務(即自動送出(autocommit)模式為 ON),執行insert、update、delete操作的時候,資料庫自動開啟事務、送出或復原事務。如下所示:
1 mysql> show variables like 'autocommit';
2 +---------------+-------+
3 | Variable_name | Value |
4 +---------------+-------+
5 | autocommit | ON |
6 +---------------+-------+
7 1 row in set
在自動送出模式下,如果沒有start transaction顯式地開始一個事務,那麼每個sql語句都會被當做一個事務執行送出操作。
通過如下方式,可以關閉autocommit;需要注意的是,autocommit參數是針對連接配接的,在一個連接配接中修改了參數,不會對其他連接配接産生影響。如果你新開一個指令視窗,會恢複到預設值。
1 mysql> set autocommit = 0;
2 Query OK, 0 rows affected
3
4 mysql> show variables like 'autocommit';
5 +---------------+-------+
6 | Variable_name | Value |
7 +---------------+-------+
8 | autocommit | OFF |
9 +---------------+-------+
10 1 row in set
如果關閉了autocommit,則所有的sql語句都在一個事務中,直到執行了commit或rollback,該事務結束,同時開始了另外一個事務。
特殊操作
在MySQL中,存在一些特殊的指令,如果在事務中執行了這些指令,會馬上強制執行commit送出事務;比如DDL語句(create table/drop table/alter table)。
不過,常用的select、insert、update和delete指令,都不會強制送出事務。
手動操作事務的兩種方式
改變預設事務送出政策
SET AUTOCOMMIT=0 禁止自動送出
SET AUTOCOMMIT=1 開啟自動送出
這個跟上面的腳本大概一緻,先設定為禁止自動送出事務,這樣執行的修改并沒有真正的到資料庫中,等commit 或者 rollback來最終确認,再手動進行事務操作。
1 --1、設定不自動送出事務
2 set autocommit=0;
3 --2、手動進行事務操作
4 commit;|rollback;
送出示例:
1 mysql> set autocommit = 0;
2 Query OK, 0 rows affected
3
4 mysql> insert into classes values(5,'初三五班');
5 Query OK, 1 row affected
6
7 mysql> commit; --送出操作
8 Query OK, 0 rows affected
9
10 mysql> select * from classes;
11 +---------+-----------+
12 | classid | classname |
13 +---------+-----------+
14 | 1 | 初三一班 |
15 | 2 | 初三二班 |
16 | 3 | 初三三班 |
17 | 4 | 初三四班 |
18 | 5 | 初三五班 |
19 +---------+-----------+
20 5 rows in set
復原示例:
1 mysql> set autocommit = 0;
2 Query OK, 0 rows affected
3
4 mysql> insert into classes values(6,'初三六班');
5 Query OK, 1 row affected
6
7 mysql> rollback; --復原操作
8 Query OK, 0 rows affected
9
10 mysql> select * from classes;
11 +---------+-----------+
12 | classid | classname |
13 +---------+-----------+
14 | 1 | 初三一班 |
15 | 2 | 初三二班 |
16 | 3 | 初三三班 |
17 | 4 | 初三四班 |
18 | 5 | 初三五班 |
19 +---------+-----------+
20 5 rows in set
正常啟用事務
START|BEGIN 開始一個事務
ROLLBACK 事務復原
COMMIT 事務确認
這是典型的MySQL事務操作,其中start transaction辨別事務開始,commit送出事務,将執行結果寫入到資料庫。如果sql語句執行出現問題,會調用rollback,復原所有已經執行成功的sql語句。
當然,也可以在事務中直接使用rollback語句進行復原。:
1 start transaction; --1、開啟事務
2 /* 1條或者n條待執行的語句 */
3 commit;|rollback; --2、手動進行事務操作
送出示例:
1 mysql> start transaction;
2 Query OK, 0 rows affected
3
4 mysql> insert into classes values(6,'初三六班');
5 Query OK, 1 row affected
6
7 mysql> commit;
8 Query OK, 0 rows affected
9
10 mysql> select * from classes;
11
12 +---------+-----------+
13 | classid | classname |
14 +---------+-----------+
15 | 1 | 初三一班 |
16 | 2 | 初三二班 |
17 | 3 | 初三三班 |
18 | 4 | 初三四班 |
19 | 5 | 初三五班 |
20 | 6 | 初三六班 |
21 +---------+-----------+
22 6 rows in set
復原示例:
1 mysql> start transaction;
2 Query OK, 0 rows affected
3
4 mysql> insert into classes values(7,'初三七班');
5 Query OK, 1 row affected
6
7 mysql> rollback;
8 Query OK, 0 rows affected
9
10 mysql> select * from classes;
11 +---------+-----------+
12 | classid | classname |
13 +---------+-----------+
14 | 1 | 初三一班 |
15 | 2 | 初三二班 |
16 | 3 | 初三三班 |
17 | 4 | 初三四班 |
18 | 5 | 初三五班 |
19 | 6 | 初三六班 |
20 +---------+-----------+
21 6 rows in set
事務儲存點的使用
事務儲存點(savepoint),指的是對事務執行過程中做位置儲存(類似我們打遊戲時的存盤點),如果你寫了一大堆的語句,但是有部分是你不想復原的,想保留修改的狀态,但是部分是你想復原的。
這時候使用savepoint是個不錯的方法。
1 mysql> start transaction; --開啟事務
2 Query OK, 0 rows affected
3
4 mysql> insert into classes values(7,'初三七班');
5 Query OK, 1 row affected
6
7 mysql> savepoint point1; --注意:這邊設定了一個存盤點
8 Query OK, 0 rows affected
9
10 mysql> insert into classes values(8,'初三八班');
11 Query OK, 1 row affected
12
13 mysql> rollback to point1; --記住這個文法,復原到存盤點,存盤點之後的語句就丢棄了
14 Query OK, 0 rows affected
15
16 mysql> commit;
17 Query OK, 0 rows affected
18
19 mysql> select * from classes; --最後輸出,确實隻有存盤點之前的成功了
20 +---------+-----------+
21 | classid | classname |
22 +---------+-----------+
23 | 1 | 初三一班 |
24 | 2 | 初三二班 |
25 | 3 | 初三三班 |
26 | 4 | 初三四班 |
27 | 5 | 初三五班 |
28 | 6 | 初三六班 |
29 | 7 | 初三七班 |
30 +---------+-----------+
31 7 rows in set
這邊需要注意 savepoint 和 rollback to savepoint 的配合使用。
隻讀事務的使用
表示在事務中執行的是一些隻讀操作,如查詢,但是不會做insert、update、delete操作,資料庫内部對隻讀事務可能會有一些性能上的優化。
1 start transaction read only;
再隻讀操作的事務中進行增、删、改操作會報錯,如下:
1 mysql> start transaction read only;
2 Query OK, 0 rows affected
3
4 mysql> select * from classes;
5 +---------+-----------+
6 | classid | classname |
7 +---------+-----------+
8 | 1 | 初三一班 |
9 | 2 | 初三二班 |
10 | 3 | 初三三班 |
11 | 4 | 初三四班 |
12 | 5 | 初三五班 |
13 | 6 | 初三六班 |
14 | 7 | 初三七班 |
15 +---------+-----------+
16 7 rows in set
17
18 mysql> insert into classes values(8,'初三八班');
19 1792 - Cannot execute statement in a READ ONLY transaction. --這邊報出異常
事務的髒讀、幻讀、不可重複讀
髒讀:讀取未送出資料
髒讀就是指當一個事務A正在通路資料,并且對資料進行了修改,而這種修改還沒有送出到資料庫中,這時,另外一個事務B也通路這個資料,然後使用了這個髒資料。舉個例子
不可重複讀:前後多次讀取資料不一緻
不可重複讀指的是在事務A中先後多次讀取同一個資料,讀取的結果不一樣,因為另外一個事務也通路該同一資料,并且可能修改這個資料,這種現象稱為不可重複讀。
髒讀與不可重複讀的差別在于:前者讀到的是其他事務未送出的資料,後者讀到的是其他事務已送出的資料。
按照正确邏輯,事務B前後兩次讀取到的資料應該一緻,這邊一次讀到的是2個,一次讀到的是3個。
幻讀:前後多次讀取,資料總量不一緻
在事務A中按照某個條件先後兩次查詢資料庫,兩次查詢結果的條數不同,這種現象稱為幻讀。不可重複讀與幻讀的差別可以通俗的了解為:前者是資料變了,後者是資料的行數變了。
按照正确邏輯,按照正确邏輯,事務B前後兩次讀取到的資料總量應該一緻
不可重複讀和幻讀的差別
(1)不可重複讀是讀取了其他事務更改的資料,針對update操作
解決:使用行級鎖,鎖定該行,事務A多次讀取操作完成後才釋放該鎖,這個時候才允許其他事務更改剛才的資料。
(2)幻讀是讀取了其他事務新增的資料,針對insert與delete操作
解決:使用表級鎖,鎖定整張表,事務A多次讀取資料總量之後才釋放該鎖,這個時候才允許其他事務新增資料。
幻讀和不可重複讀都是指的一個事務範圍内的操作受到其他事務的影響了。隻不過幻讀是重點在插入和删除,不可重複讀重點在修改
事務的隔離級别
SQL标準中事務的隔離性(Isolation)定義了四種隔離級别,并規定了每種隔離級别下上述幾個(髒讀、不可重複讀、幻讀)問題是否存在。
一般來說,隔離級别越低,系統開銷越低,可支援的并發越高,但隔離性也越差。隔離級别與讀問題的關系如下:
隔離級别的分類
注意:幻讀隻會在 可重複讀 級别中才會出現,其他級别下不存在。
檢視|修改 隔離級别
檢視目前的隔離級别,預設應該都是可重複讀(Repeatable Read):
1 mysql> show variables like 'transaction_isolation';
2 +-----------------------+-----------------+
3 | Variable_name | Value |
4 +-----------------------+-----------------+
5 | transaction_isolation | REPEATABLE-READ |
6 +-----------------------+-----------------+
7 1 row in set
修改隔離級别:
找到MySQL安裝目錄中的my.init檔案,會看到目前的隔離級别:REPEATABLE-READ。
1 # 隔離級别設定,READ-UNCOMMITTED讀未送出,READ-COMMITTED讀已送出,REPEATABLE-READ可重複讀,SERIALIZABLE串行
2 transaction-isolation=REPEATABLE-READ
修改後重新開機MySQL即可,下面的各項操作都是在修改了對應的隔離級别之後的操作。
READ-UNCOMMITTED:讀未送出
事物A和事物B,事物B未送出的資料,事物A可以讀取到,這裡讀取到的資料叫做“髒資料”,這種隔離級别最低,一般理論上存在,資料庫隔離級别大都高于該級别。
舉個例子:學校新增加了7班和8班,教務主任進去錄入了8班的班級資訊,但是該事務并未送出,而8班班主任正好去檢視班級資訊,發現是存在的,摩拳擦掌準備錄入這個班級的學生資訊。可是教務主任發現班級資訊寫錯了,應該先錄入7班,于是迅速復原了該事務。
最後8班的資訊消失了。出現上述情況,就是我們所說的髒讀 ,兩個并發的事務,“事務A:查詢班級資訊”、“事務B:錄入班級資訊”,事務A讀取了事務B尚未送出的資料。
資料基礎:
1 mysql> select * from classes;
2 +---------+-----------+
3 | classid | classname |
4 +---------+-----------+
5 | 1 | 初三一班 |
6 | 2 | 初三二班 |
7 | 3 | 初三三班 |
8 | 4 | 初三四班 |
9 | 5 | 初三五班 |
10 | 6 | 初三六班 |
11 | 7 | 初三七班 |
12 +---------+-----------+
13 7 rows in set
說明:
事務A-T2:隻有7條資料,事務A-T6:有8條資料,事務B-T6并未送出,此時事務A已經看到了事務B插入的資料,出現了髒讀。
事務A-T2:隻有7條資料,T6-A:有8條資料,查詢到的結果不一樣,出現不可重複讀。
結論:讀未送出情況下,可以讀取到其他事務還未送出的資料,多次讀取結果不一樣,出現了髒讀、不可重複讀
READ-COMMITTED:讀已送出
事物A和事物B,事物B送出完的資料,事物A才能讀取到
這種隔離級别高于讀未送出:即對方事物送出之後的資料,我目前事物才能讀取到
這種隔離級别可以避免“髒資料” ,但會導緻“不可重複讀取”
資料基礎跟上面一樣
說明:
事務A-T5:隻有7條資料,A看不到B新增的第8條資料,說明沒有髒讀。
事務A-T5:隻有7條資料,事務A-T7:讀到了8條資料,此時事務B已經送出了事務,事務A讀取到了事務B送出的資料,說明可以讀取到已送出的資料。
事務A-T5 和 事務A-T7:兩次讀取的資料結果不一樣,說明不可重複讀。
結論:讀已送出情況下,無法讀取到其他事務還未送出的資料,可以讀取到其他事務已經送出的資料,多次讀取結果不一樣,未出現髒讀,出現了讀已送出、不可重複讀。
REPEATABLE-READ:可重複讀
可重複讀是MySQL預設事務隔離級别
事務A和事務B,事務B送出之後的資料,事務A讀取不到,即對方送出之後的資料,還是讀取不到
事務B是可重複讀取資料,隔離級别高于讀已送出,這種隔離級别可以避免“不可重複讀取”,達到可重複讀取,但是可能導緻“幻讀”
資料基礎:
1 mysql> select * from classes;
2 +---------+-----------+
3 | classid | classname |
4 +---------+-----------+
5 | 1 | 初三一班 |
6 | 2 | 初三二班 |
7 | 3 | 初三三班 |
8 | 4 | 初三四班 |
9 | 5 | 初三五班 |
10 | 6 | 初三六班 |
11 | 7 | 初三七班 |
12 | 8 | 初三八班 |
13 +---------+-----------+
14 8 rows in set
說明:
事務A-T4、事務A-T7:讀到的是八條資料,事務B-T6:有資料,A事務下讀不到B事務産生的資料,說明沒有髒讀。
事務A-T7:讀到的是9條資料,這時事務B已經commit,事務A看不到事務B已送出的資料,A事務下兩次讀的結果一樣,說明可重複讀。
事務A-T9:讀到的是9條資料。
結論:可重複讀情況下,未出現髒讀,未讀取到其他事務已送出的資料,多次讀取結果一緻,即可重複讀。
SERIALIZABLE:串行
SERIALIZABLE會讓并發的事務串行執行。事務A和事務B,事務A在操作資料庫時,事務B隻能排隊等待,這種隔離級别很少使用,吞吐量太低,使用者體驗差
這種級别可以避免“幻讀”,每一次讀取的都是資料庫中真實存在資料,事務A與事務B串行,而不并發
資料基礎同上
按時間順序運作上面的指令,會發現事務B-T4這樣會被阻塞,直到事務A執行完畢T6步驟。
可以看出來,事務隻能串行執行了。串行情況下不存在髒讀、不可重複讀、幻讀的問題了。
隔離級别選擇注意點
1、讀已送出(READ-COMMITTED)通常用的比較多,也是MySQL預設選項。
2、具體選擇哪種需要結合具體的業務來選擇,隔離級别越高,并發性也低,比如最進階别SERIALIZABLE會讓事物串行執行,并發操作變成串行了,會導緻系統性能直接降低
事務典型用法
1 DROP PROCEDURE IF EXISTS t_test;
2 DELIMITER //
3 CREATE PROCEDURE t_test()
4 BEGIN
5 DECLARE t_error INTEGER;
6 DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1;
7 START TRANSACTION;
8 insert into students(studentname,score,classid) VALUE('A',99,3);
9 insert into students(studentname,score,classid) VALUE('A','lala',3); --這邊執行錯誤,兩個語句都會被復原
10 IF t_error = 1 THEN
11 ROLLBACK; -- 有錯誤復原
12 ELSE
13 COMMIT; --沒錯誤送出
14 END IF;
15 END //DELIMITER;
16 CALL t_test();
這個是典型的用法,score是decimal類型,這兩個語句都會被復原。
MVCC了解
RR解決髒讀、不可重複讀、幻讀等問題,使用的是MVCC(Multi-Version Concurrency Control)協定,即多版本的并發控制協定。
有多個請求來讀取表中的資料時可以不采取任何操作,但是多個請求裡有讀請求,又有修改請求時必須有一種措施來進行并發控制。不然很有可能會造成不一緻。
讀寫鎖,解決上述問題很簡單,隻需用兩種鎖的組合來對讀寫請求進行控制即可,這兩種鎖被稱為:
1、共享鎖(shared lock),又叫做 讀鎖
讀鎖是可以共享的,或者說多個讀請求可以共享一把鎖讀資料,不會造成阻塞。
2、排他鎖(exclusive lock),又叫做 寫鎖
寫鎖會排斥其他所有擷取鎖的請求,一直阻塞,直到寫入完成釋放鎖。
通過讀寫鎖,可以做到讀讀可以并行,但是不能做到寫讀,寫寫并行。這邊了解一下,後續專門一篇來說明MVCC的原理和實作機制分析。
總結
1、認識ACID(原子性、一緻性、隔離性、持久性)特性及其實作原理
2、了解事務的髒讀、幻讀、不可重複讀
3、了解事務的隔離級别以及原理
為幫助開發者們提升面試技能、有機會入職BATJ等大廠公司,特别制作了這個專輯——這一次整體放出。
大緻内容包括了: Java 集合、JVM、多線程、并發程式設計、設計模式、Spring全家桶、Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、MongoDB、Redis、MySQL、RabbitMQ、Kafka、Linux、Netty、Tomcat等大廠面試題等、等技術棧!
歡迎大家關注公衆号【Java爛豬皮】,回複【666】,擷取以上最新Java後端架構VIP學習資料以及視訊學習教程,然後一起學習,一文在手,面試我有。
每一個專欄都是大家非常關心,和非常有價值的話題,如果我的文章對你有所幫助,還請幫忙點贊、好評、轉發一下,你的支援會激勵我輸出更高品質的文章,非常感謝!