探秘數據庫中的事務

本文包括:

1、事務概念

2、MySQL管理事務

3、JDBC控制事務進程

4、事務的特性(ACID)

5、事務的隔離級別

6、事務的丟失更新問題(lost update)

1、事務的概念

  1. 事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
    例如:A——B轉帳,對應于如下兩條sql語句

     update from account set money=money-100 where name=‘a’; 
     update from account set money=money+100 where name=‘b’;
    
  2. 自動提交

    • MySQL 數據庫 默認情況下 一條SQL就是一個單獨事務,事務是自動提交的

    • Oracle 數據庫 默認情況下 事務不是自動提交 ,所有SQL都將處于一個事務中,你需要手動進行commit提交/rollback回滾

2、MySQL管理事務

  1. 在事務管理中執行sql,使用數據庫內臨時表保存,在沒有進行事務提交或者回滾,其它用戶無法看到事務操作結果的

  2. SQL語言中只有DML才能被事務管理,如insert update delete

  3. SQL語句:

    • start transaction 開啟事務 (所有對數據表增加、修改、刪除操作 臨時表進行)

    • rollback 回滾事務 (取消剛剛操作)

    • commit 提交事務 (確認剛才操作)

    • savepoint my_savepoint 保存點(回滾點,像打游戲時的存檔)

    • rollback to savepoint my_savepoint 回滾到指定的保存點

3、JDBC控制事務進程

  1. 當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行。

  2. JDBC控制事務語句:

     Connection.setAutoCommit(false); //  相當于SQL語句的start transaction
     
     Connection.rollback();  // 相當于SQL語句的rollback
     
     Connection.commit();  // 相當于SQL語句的commit
    
     Savepoint sp = conn.setSavepoint(); // 相當于SQL語句的savepoint my_savepoint
    
     Conn.rollback(sp); // 相當于SQL語句的rollback to savepoint my_savepoint
    
  3. 注意:

    • 使用保存點進行事務控制時,回滾到指定保存點之后,必須要調用commit

    • 如果直接使用沒有保存點的rollback命令,那么事務中的所有保存點都將被忽略,并且撤銷整個事務(這就是普通rollback之后不需要調用commit的原因)

  4. demo:

    其中JDBCUtils文件源碼在另外一篇文章《Java與數據庫的橋梁——JDBC》

     package cn.itcast.test;
     
     import java.sql.Connection;
     import java.sql.PreparedStatement;
     import java.sql.SQLException;
     import java.sql.Savepoint;
     
     import org.junit.Test;
     
     import cn.itcast.utils.JDBCUtils;
     
     public class TransferTest {
         @Test
         public void demo3() {
             // 創建person表,向表中插入20000條數據 ------ 如果插入過程中發生錯誤,則回滾到插入數據條數為1000的整數倍的時候
             // PreparedStatement 批處理
             Connection conn = null;
             PreparedStatement stmt = null;
             Savepoint savepoint = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 開啟事務
                 conn.setAutoCommit(false);
                 // 保存一次
                 savepoint = conn.setSavepoint();
     
                 String sql = "insert into person values(?,?)";
                 // 預編譯SQL
                 stmt = conn.prepareStatement(sql);
     
                 for (int i = 1; i <= 20000; i++) {
                     stmt.setInt(1, i);
                     stmt.setString(2, "name" + i);
     
                     // 添加到批處理
                     stmt.addBatch();
     
                     if (i == 4699) {
                         int d = 1 / 0;
                     }
     
                     // 每隔200向數據庫發送一次
                     if (i % 200 == 0) {
                         stmt.executeBatch();
                         stmt.clearBatch();
                     }
     
                     if (i % 1000 == 0) {// 1000整數倍
                         // 保存 回滾點
                         savepoint = conn.setSavepoint();
                     }
                 }
     
                 stmt.executeBatch();// 為了確保緩存sql都提交了
     
                 // 沒有錯誤
                 conn.commit();
     
             } catch (Exception e) {
                 // 回滾事務,回滾存儲點
                 try {
                     conn.rollback(savepoint);
                     conn.commit();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo2() {
             // 模擬轉賬操作,使用事務管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 1、在連接獲得后,開啟事務 start transaction
                 conn.setAutoCommit(false);// 關閉自動提交
     
                 // 沒有用事務管理,兩句sql 就是兩個事務
                 String sql1 = "update account set money = money - 100 where name='aaa'";
                 String sql2 = "update account set money = money +100 where name ='bbb'";
     
                 stmt = conn.prepareStatement(sql1);
                 stmt.executeUpdate();// 執行 update操作
     
                 // int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 執行 update操作
     
                 // 2、兩句SQL 都執行成功,事務commit
                 System.out.println("事務提交!");
                 conn.commit();
     
             } catch (Exception e) {
                 // 3、在執行轉賬過程中 發生錯誤,將兩句sql 進行回滾
                 System.out.println("事務回滾!");
                 try {
                     conn.rollback();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
     
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo1() {
             // 模擬轉賬操作,先不使用事務管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 沒有用事務管理,兩句sql 就是兩個事務
                 String sql1 = "update account set money = money - 100 where name='aaa'";
                 String sql2 = "update account set money = money +100 where name ='bbb'";
     
                 stmt = conn.prepareStatement(sql1);
                 stmt.executeUpdate();// 執行 update操作
     
                 int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 執行 update操作
     
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     }
    

4、事務的特性(ACID)

  • 原子性(Atomicity)原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。

  • 一致性(Consistency)事務前后數據的完整性必須保持一致。

  • 隔離性(Isolation)事務的隔離性是指多個用戶并發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所干擾,多個并發事務之間數據要相互隔離。

  • 持久性(Durability)持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。

企業開發中一定要保證事務原子性,事務最復雜問題都是由事務隔離性引起的

5、事務的隔離級別

  1. 不考慮事務隔離將引發哪些問題:臟讀、不可重復讀、虛讀

    • 臟讀:一個事務讀取另一個事務未提交數據(數據庫隔離中最重要的問題)

      這是非常危險的,假設A要在B的網店購物,A向B轉帳100元,對應sql語句如下所示

         1.update account set money=money+100 while name=‘b’;    
         2.update account set money=money-100 while name=‘a’;
      

      當第1條sql執行完,第2條還沒執行(A未提交時),如果此時B查詢自己的帳戶,就會發現自己多了100元錢,立即發貨,如果A等B發貨后再回滾,B就會損失貨物。

    • 不可重復讀:一個事務讀取另一個事務未提交和已提交數據,兩次讀取結果不同(有時并不一定是壞問題)。

      1. 例如銀行想查詢A帳戶余額,第一次查詢A帳戶為200元,此時A向帳戶存了100元并提交了,銀行接著又進行了一次查詢,此時A帳戶為300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。
      1. 不可重復讀與臟讀的區別是:臟讀是讀取前一事務未提交的臟數據,不可重復讀是重新讀取了前一事務已提交的數據。
    • 虛讀:一個事務讀取另一個事務插入數據,造成在一個事務中兩次讀取記錄條數不同(比不可重復讀危害性更低,很多數據庫不認為虛讀是問題)。

      1. 如丙存款100元未提交,這時銀行做報表統計account表中所有用戶的總額為500元,然后丙提交了,這時銀行再統計發現帳戶為600元了,造成虛讀同樣會使銀行不知所措,到底以哪個為準。
      1. 虛讀與不可重復讀的區別:不可重復讀讀取update的數據 ,虛讀讀取insert的數據。
  2. 數據庫共定義了四種隔離級別:

    • Serializable : 可解決 臟讀、不可重復讀、虛讀情況的發生。串行處理 ---解決三類問題

    • Repeatable read :可以解決 不可重復讀、臟讀,會發生虛讀 ------MySQL 默認級別

    • read committed : 可以 解決臟讀 ,會發生 不可重復讀、虛讀(讀已提交) -------Oracle默認級別

    • read uncommitted : 最低級別,會導致三類問題發生 (讀未提交)

    Serializable > Repeatable read > read committed > read uncommitted

    數據庫隔離問題危害:臟讀> 不可重復讀 > 虛讀,

    安全級別越高,處理效率越低;安全級別越低,效率越高。所以一般數據庫把默認級別定位中間的兩級。

  3. 有關SQL語句:

    • set transaction isolation level XXX; 設置事務隔離級別

    • select @@tx_isolation; 查詢當前事務隔離級別

  4. JDBC控制數據庫隔離級別:Connection類

     Connection setTransactionIsolation(int level);
    

    level字段摘要:

    • static int TRANSACTION_NONE 指示事務不受支持的常量。
    • static int TRANSACTION_READ_COMMITTED 指示不可以發生臟讀的常量;不可重復讀和虛讀可以發生。
    • static int TRANSACTION_READ_UNCOMMITTED 指示可以發生臟讀 (dirty read)、不可重復讀和虛讀 (phantom read) 的常量。
    • static int TRANSACTION_REPEATABLE_READ 指示不可以發生臟讀和不可重復讀的常量;虛讀可以發生。
    • static int TRANSACTION_SERIALIZABLE 指示不可以發生臟讀、不可重復讀和虛讀的常量。

6、事務的丟失更新問題(lost update)

  1. 丟失更新問題描述:兩個事務查詢同一行,再先后更新這一行,但這些事務彼此之間都不知道其它事務進行的修改,所以因此第二個更改覆蓋了第一個修改。


  2. 首先介紹:在MySQL內部有兩種常用鎖:讀鎖和寫鎖

    在mysql中默認情況下,在事務中當你修改數據,自動為數據加鎖,防止兩個事務同時修改數據 ---- 即讀鎖

    事務和鎖和不可分開的,鎖一定是在事務中使用,當事務關閉鎖自動釋放。

    • 讀鎖(共享鎖) 一張表可以添加多個讀鎖,如果表已經添加了讀鎖(該讀鎖不是當前事務添加的),則該表不可以修改

      • select * from account lock in share mode;

      • 共享鎖非常容易發生死鎖(兩個事務都為同一張表添加了共享鎖,則該表在兩個事務中都不能修改)

    • 寫鎖(排它鎖) 一張表只能加一個排它鎖,如果表已經添加了寫鎖,則該表不可以查詢,也不可以修改

      • select * from account for update ;

      • 如果一張表想添加排它鎖,則之前表一定沒有加過共享鎖和排他鎖,否則無法添加排它鎖(排他鎖和其它共享鎖、排它鎖都具有互斥效果)

  1. 丟失更新問題的解決有兩種方法:

    • 悲觀鎖( Pessimistic Locking )

        select * from table lock in share mode(讀鎖、共享鎖)
        select * from table for update (寫鎖、排它鎖)
      
    • 樂觀鎖( Optimistic Locking )
      通過時間戳字段

  2. 悲觀鎖原理:使用數據庫內部鎖機制,進行table的鎖定,在A修改數據時,A就將數據鎖定,B此時無法進行修改 ----- 無法發生兩個事務同時修改

    由1、2點可知,悲觀鎖可以使用排它鎖實現

    為什么叫悲觀鎖?

    因為假設丟失更新會發生,所以是悲觀的。

  3. 樂觀鎖原理:使用的不是數據庫鎖機制,而是一個特殊時間戳標記字段,假設兩個事務查詢了同一個記錄,A事務修改了該記錄,則時間戳字段會更新,若B事務再修改該記錄,此時把之前查詢的時間戳字段與當前時間戳字段比較,得知數據是否發生了并發訪問。

    表額外增加一個timestamp類型的字段,即時間戳字段,該字段在新增數據時可設為null,然后會自動生成當前時間,同時在修改該記錄時,也會自動更新為當前時間。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容