1. 程式人生 > >MySQL學習之事務及其四大特性和隔離級別

MySQL學習之事務及其四大特性和隔離級別

事務

  • 定義:
    事務是指資料庫中的一組邏輯操作,這個操作的特點就是在該組邏輯中,所有的操作要麼全部成功,要麼全部失敗。在各個資料具有特別緊密的聯絡時,最好是使用資料庫的事務來完成邏輯處理。
    說的通俗點就是,我們執行一個事件時,光靠簡簡單單的一句sql語句是無法完成的,這時候我們就需要多條語句組合完成,但是我們不知道在哪一句sql執行時會發生錯誤,這時就必須用到事務;使得多條sql語句成為一個整體,只有它們全部都執行成功時,更改才會發生,否則就不成功,回滾到最初的狀態;

這裡我們來看一個例子,以幫助我們更好的理解事務;
例如路人甲給路人乙轉賬1000元,對應於如下兩條SQL命令;

	update money set balance=balance-1000 where name=’甲’;
    update money set balance=balance+1000 where name=’乙’;

在上面兩條SQL語句中,任意一條SQL執行過程中出現了錯誤,那麼就有可能造成甲與乙兩人最後總金額的錯誤。
但如果是使用事務來處理,即使上面的轉賬過程出現了錯誤,那麼之前執行的資料庫操作即使成功也會一併回滾,形成所有的SQL操作全部失敗,保證所有人的金額不變;

  • 事務執行語句:
    在我們之前的sql語句執行時,我們好像沒有使用事務,但其實在我們剛剛使用MySQL資料庫的時候我們就已經在使用事務了;我們每執行一條sql語句,MySQL都會預設給我們開啟一個事務,並且執行完畢後自動的提交,但其實事務的開啟、提交、回滾都有特定的語句的;
	事務開啟: start transaction/begin;
	事務提交:commit;
	事務回滾:rollback;

那如果我們就要完成上面的例子,實現轉賬操作,用事務來做的程式碼就是這樣:

比如甲,乙 兩人的賬戶裡本來就一人只有1000塊,現在甲要給乙轉賬1000元;

開啟事務,模仿中間發生錯誤;

首先甲向乙轉賬1000元;

	start transaction;
	update money set balance=balance-1000 where name=’甲’;

在這裡插入圖片描述

這時候我們看到事務正常開始,並執行了甲賬戶的錢轉賬出去的操作;
可比如我們這個時候發生了錯誤,我們用關閉MySQL模擬錯誤


然後我們再回頭查詢甲乙的賬戶;
在這裡插入圖片描述

我們發現由於我們沒有進行 commit(提交) 操作;MySQL自動幫我們rollback(回滾) 了,之前執行的SQL語句全部不算成功,因此即使資料庫可能發生了錯誤,使用者金額還是能由事務保證不出意外;

開啟事務,成功完成轉賬;

這時我們要完成整個甲向乙轉賬的操作:

	start transaction;
	update money set balance=balance-1000 where name=’甲’;
    	update money set balance=balance+1000 where name=’乙’;
    	commit;

在這裡插入圖片描述
當上面的commit(提交)命令執行了,那就說明我們的整個操作完成了,這時就算我們關閉MySQL,模擬錯誤發生,回來再查詢,甲乙兩人的balance(餘額)也已經發生了改變;
在這裡插入圖片描述

rollback(回滾)

回滾就時將之前所有已執行的SQL全部視為無效;
也就是說再沒有執行commit(提交)命令之前,不管我們前面做了多少的增刪改操作,一句rollback(回滾),之前的命令全部作廢,而好就好在MySQL在我們發生錯誤時,會自動回滾,幫我們儲存原來的狀態,以防止資料的錯誤;

在這裡插入圖片描述

使用rollback命令會回滾該事務內所有之前執行的SQL命令,不會只回滾前面一條SQL命令,因此即使我們對A和B的金額操作了多次,最終還是回到事務開啟前的金額數:


JDBC操作資料庫事務;

上面的事務操作我們都是在mysql環境下操作的,現在我們學習一下在Java環境下,即jdbc操作資料庫事務;

上面我們說過資料庫對於事務是預設自動提交的,也就是發一條SQL命令則資料庫就執行一條。而對於JDBC而言,當向資料庫獲取一個連結Connection物件,在預設情況下通過Connection物件傳送的SQL命令也是預設自動提交事務的。那我們要模仿事務的開啟、提交、回滾等就必須關閉 jdbc 的自動提交功能。

  1. 於是我們首先需要用 setAutoCommit(false); 關閉自動提交,同時這句語句也相當於 start transaction,開啟事務的語句了;
  2. 然後我們執行一些sql語句,在JDBC已經將自動提交關閉的情況下需要提交事務,則呼叫Connection物件的commit()方法即可。
  3. 在JDBC已經將自動提交關閉的情況下需要回滾事務,則呼叫Connection物件的rollback(…)方法即可。
    rollback方法如果是無參,則回滾前面所有已執行的SQL命令;如果是有參,則可以指定回滾點,保留前面部分指定的已執行的SQL命令。

瞭解了jdbc操作資料庫的基本操作流程,下面我們就來看看具體程式碼和演示結果;
首先我們要和資料庫建立連線,這裡我們將取得連線的方法寫成一個工具類,每次獲得Connection的物件即可;

		//driver=com.mysql.jdbc.Driver
	static final String URL= "jdbc:mysql://localhost:3306/test";
    	static final String USERNAME = "root";
    	static final String PASSWORD = "root";
    	public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }

這裡有一個註冊驅動的語句,在jdk 1.8以後,註冊驅動這個操作就可以省去了,idea可以自動識別對應驅動;

然後我們就來嘗試用jdbc來操作資料庫的事務;
我們就以上面甲給乙轉賬1000 元做為例子來說吧:
首先我們還是準備好原始的資料,甲和乙兩人分別都只有1000塊:
在這裡插入圖片描述

然後我們來看看程式碼:

public static void main(String[] args) {
        try(Connection conn = JdbcUtils.getConnection()){

            conn.setAutoCommit(false);  //事務開始,關閉自動提交;

            try(Statement stat = conn.createStatement()){
                //執行sql語句1
                stat.executeUpdate("update money set balance=balance-1000 where name='甲'");
                //執行sql語句2
                stat.executeUpdate("update money set balance=balance+1000 where name='乙'");
            }
            conn.commit();   //手動提交事務;
			 System.out.println("轉賬完成!");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

在這裡我們用了一種try-with-resources的方法,這種特殊的try方法保證了每個聲明瞭的資源在語句結束的時候都會被關閉。 這可以省去我們最後釋放Connection 和 Statement物件的操作;如果你不想用這種方法,也可以使用原來的 try{} 方法,並且在最後的finally{ conn.close(); stat.close();};
在這裡插入圖片描述
在這裡插入圖片描述

我們看到上面簡單的程式碼就完成了事務的一系列操作。可以看到使用者甲向用戶乙確實轉賬了1000元。

JDBC操作中遇到錯誤;

同樣如果我們在操作的過程中,如果遇到了錯誤,同樣資料庫在會幫我們rollback(回滾);
在使用JDBC進行事務處理中,我們新增一個顯而易見的錯誤:int i = 1/0 ;

 public static void main(String[] args) {

        try(Connection conn = JdbcUtils.getConnection()){

            conn.setAutoCommit(false);  //事務開始,關閉自動提交;

            try(Statement stat = conn.createStatement()){
                //執行sql語句1
                stat.executeUpdate("update money set balance=balance-1000 where name='甲'");
                int i = 1/0;
                //執行sql語句2
                stat.executeUpdate("update money set balance=balance+1000 where name='乙'");
            }
            conn.commit();
            System.out.println("轉賬完成!");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

當我們執行這個Java方法時,由於設定了int i = 1/0這個邏輯錯誤,程式會丟擲異常,但是因為丟擲異常後,後面的程式碼不再執行,也就是說程式無法執行到提交事務conn.commit()方法處,因此資料庫將會回滾該事務所有的操作,因此金額還是原來那樣:
在這裡插入圖片描述

回滾到指定位置

我們上面說過回滾的方法可以新增引數,這個引數就是我們可以在程式碼中添加回滾點;
通過連結Connection物件的setSavepoint() 方法即可在該方法所在的位置設定回滾點物件,當呼叫rollback(回滾點物件)方法即可將事務回滾到這個位置。這樣在執行回滾之後,再次呼叫提交事務(Commit), 則回滾點之前的SQL還是執行的。

 public static void main(String[] args) {
		 Connection conn = null;
        try{
	     conn = JdbcUtils.getConnection()
            conn.setAutoCommit(false);  //事務開始,關閉自動提交;

            try(Statement stat = conn.createStatement()){
                //執行sql語句1
                stat.executeUpdate("update money set balance=balance-1000 where name='甲'");
                
		 Savepoint sp = conn.setSavepoint();   //設定回滾點;
		 
                int i = 1/0;    //模擬錯誤發生
                //執行sql語句2
                stat.executeUpdate("update money set balance=balance+1000 where name='乙'");
            }
            conn.commit();
            System.out.println("轉賬完成!");
        } catch (SQLException e) {
            conn.rollback();
            conn.commit();
        }finally{
        	stat.close();
        	conn.close();
        }
    }

在這裡插入圖片描述

可以看到結果正如我們希望的那樣,由於甲的操作在回滾點之前,又因為執行回滾之後還執行了提交事務,因此回滾點之前的SQL命令還是可以被執行成功的。因此切記,要想使用回滾點,一定要在回滾之後再次提交事務,否則設定回滾點是沒有意義的。


資料庫的四大特性和隔離級別

  • 事務的四大特性:
    ⑴ 原子性(Atomicity)
      原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,這和前面兩篇部落格介紹事務的功能是一樣的概念,因此事務的操作如果成功就必須要完全應用到資料庫,如果操作失敗則不能對資料庫有任何影響。
      
    ⑵ 一致性(Consistency)
      一致性是指事務必須使資料庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。
      拿轉賬來說,假設使用者A和使用者B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個使用者的錢相加起來應該還得是5000,這就是事務的一致性。
      
    ⑶ 隔離性(Isolation)
      隔離性是當多個使用者併發訪問資料庫時,比如操作同一張表時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作所幹擾,多個併發事務之間要相互隔離。
      關於事務的隔離性資料庫提供了多種隔離級別,稍後會介紹到。
      
    ⑷ 永續性(Durability)
      永續性是指一個事務一旦被提交了,那麼對資料庫中的資料的改變就是永久性的,即便是在資料庫系統遇到故障的情況下也不會丟失提交事務的操作。

  • 隔離級別:
    隔離級別是事務的隔離性中的特點,關於事務的隔離性資料庫提供了多種隔離級別;
    有不同的隔離級別,
    隔離級別越低,併發性越好,但資料的一致性差
    隔離級別越高,併發性差,但資料的一致性高
    由低到高四種:
    讀未提交 < 讀提交 < 可重複讀(mysql預設) < 序列化讀

錯誤的級別由高到低
髒讀 , 不可重複讀, 幻讀

髒讀(讀未提交)

7369的工資1000
事務1                                              事務2
begin                                              begin
修改7369的工資為8000
                                                   select * sal from emp where empno=7369;// 8000
rollback

髒讀是指在一個事務處理過程裡讀取了另一個未提交的事務中的資料。

將隔離級別提高到讀提交,可以避免髒讀

不可重複讀

一邊查詢,另一邊做update操作
一個事務內多次查詢結果不一致

7369的工資1000
事務1                                              事務2
begin                                              begin
                                                   select * sal from emp where empno=7369; // 1000
修改7369的工資為8000
commit;
                                                   select * sal from emp where empno=7369; // 8000

事務T1在讀取某一資料,而事務T2立馬修改了這個資料並且提交事務給資料庫,事務T1再次讀取該資料就得到了不同的結果,傳送了不可重複讀。

要避免髒讀、不可重複讀:將隔離級別提高到可重複讀隔離級別

幻讀

一邊查詢,另一邊做insert操作
如事務T1對一個表中所有的行的某個資料項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行資料項,而這個資料項的數值還是為“1”並且提交給資料庫。而操作事務T1的使用者如果再檢視剛剛修改的資料,會發現還有一行沒有修改,其實這行是從事務T2中新增的,就好像產生幻覺一樣,這就是發生了幻讀。

要避免髒讀、不可重複讀、幻讀:將隔離級別提高到序列化讀

所謂的序列化讀就是把多版本併發退化到鎖的併發控制:select語句上會被偷偷加上共享鎖