探秘數(shù)據(jù)庫中的事務(wù)

本文包括:

1怀薛、事務(wù)概念

2刺彩、MySQL管理事務(wù)

3、JDBC控制事務(wù)進程

4枝恋、事務(wù)的特性(ACID)

5创倔、事務(wù)的隔離級別

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

1焚碌、事務(wù)的概念

  1. 事務(wù)指邏輯上的一組操作畦攘,組成這組操作的各個單元,要不全部成功十电,要不全部不成功知押。
    例如:A——B轉(zhuǎn)帳叹螟,對應(yīng)于如下兩條sql語句

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

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

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

2、MySQL管理事務(wù)

  1. 在事務(wù)管理中執(zhí)行sql静盅,使用數(shù)據(jù)庫內(nèi)臨時表保存良价,在沒有進行事務(wù)提交或者回滾,其它用戶無法看到事務(wù)操作結(jié)果的

  2. SQL語言中只有DML才能被事務(wù)管理蒿叠,如insert update delete

  3. SQL語句:

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

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

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

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

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

3痊银、JDBC控制事務(wù)進程

  1. 當Jdbc程序向數(shù)據(jù)庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數(shù)據(jù)庫提交在它上面發(fā)送的SQL語句魂务。若想關(guān)閉這種默認提交方式曼验,讓多條SQL在一個事務(wù)中執(zhí)行。

  2. JDBC控制事務(wù)語句:

     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. 注意:

    • 使用保存點進行事務(wù)控制時粘姜,回滾到指定保存點之后鬓照,必須要調(diào)用commit

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

  4. demo:

    其中JDBCUtils文件源碼在另外一篇文章《Java與數(shù)據(jù)庫的橋梁——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() {
             // 創(chuàng)建person表,向表中插入20000條數(shù)據(jù) ------ 如果插入過程中發(fā)生錯誤号显,則回滾到插入數(shù)據(jù)條數(shù)為1000的整數(shù)倍的時候
             // PreparedStatement 批處理
             Connection conn = null;
             PreparedStatement stmt = null;
             Savepoint savepoint = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 開啟事務(wù)
                 conn.setAutoCommit(false);
                 // 保存一次
                 savepoint = conn.setSavepoint();
     
                 String sql = "insert into person values(?,?)";
                 // 預(yù)編譯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向數(shù)據(jù)庫發(fā)送一次
                     if (i % 200 == 0) {
                         stmt.executeBatch();
                         stmt.clearBatch();
                     }
     
                     if (i % 1000 == 0) {// 1000整數(shù)倍
                         // 保存 回滾點
                         savepoint = conn.setSavepoint();
                     }
                 }
     
                 stmt.executeBatch();// 為了確保緩存sql都提交了
     
                 // 沒有錯誤
                 conn.commit();
     
             } catch (Exception e) {
                 // 回滾事務(wù)臭猜,回滾存儲點
                 try {
                     conn.rollback(savepoint);
                     conn.commit();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo2() {
             // 模擬轉(zhuǎn)賬操作,使用事務(wù)管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 1押蚤、在連接獲得后蔑歌,開啟事務(wù) start transaction
                 conn.setAutoCommit(false);// 關(guān)閉自動提交
     
                 // 沒有用事務(wù)管理,兩句sql 就是兩個事務(wù)
                 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();// 執(zhí)行 update操作
     
                 // int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 執(zhí)行 update操作
     
                 // 2揽碘、兩句SQL 都執(zhí)行成功次屠,事務(wù)commit
                 System.out.println("事務(wù)提交!");
                 conn.commit();
     
             } catch (Exception e) {
                 // 3雳刺、在執(zhí)行轉(zhuǎn)賬過程中 發(fā)生錯誤劫灶,將兩句sql 進行回滾
                 System.out.println("事務(wù)回滾!");
                 try {
                     conn.rollback();
                 } catch (SQLException e1) {
                     e1.printStackTrace();
                 }
     
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     
         @Test
         public void demo1() {
             // 模擬轉(zhuǎn)賬操作掖桦,先不使用事務(wù)管理
             Connection conn = null;
             PreparedStatement stmt = null;
     
             try {
                 conn = JDBCUtils.getConnection();
                 // 沒有用事務(wù)管理本昏,兩句sql 就是兩個事務(wù)
                 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();// 執(zhí)行 update操作
     
                 int d = 1 / 0;
     
                 stmt = conn.prepareStatement(sql2);
                 stmt.executeUpdate();// 執(zhí)行 update操作
     
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 JDBCUtils.release(stmt, conn);
             }
         }
     }
    

4、事務(wù)的特性(ACID)

  • 原子性(Atomicity)原子性是指事務(wù)是一個不可分割的工作單位枪汪,事務(wù)中的操作要么都發(fā)生涌穆,要么都不發(fā)生怔昨。

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

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

  • 持久性(Durability)持久性是指一個事務(wù)一旦被提交原叮,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的赫编,接下來即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響。

企業(yè)開發(fā)中一定要保證事務(wù)原子性奋隶,事務(wù)最復(fù)雜問題都是由事務(wù)隔離性引起的

5擂送、事務(wù)的隔離級別

  1. 不考慮事務(wù)隔離將引發(fā)哪些問題:臟讀、不可重復(fù)讀唯欣、虛讀

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

      這是非常危險的嘹吨,假設(shè)A要在B的網(wǎng)店購物,A向B轉(zhuǎn)帳100元境氢,對應(yīng)sql語句如下所示

         1.update account set money=money+100 while name=‘b’;    
         2.update account set money=money-100 while name=‘a(chǎn)’;
      

      當?shù)?條sql執(zhí)行完蟀拷,第2條還沒執(zhí)行(A未提交時),如果此時B查詢自己的帳戶萍聊,就會發(fā)現(xiàn)自己多了100元錢问芬,立即發(fā)貨,如果A等B發(fā)貨后再回滾寿桨,B就會損失貨物此衅。

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

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

      1. 如丙存款100元未提交狗热,這時銀行做報表統(tǒng)計account表中所有用戶的總額為500元钞馁,然后丙提交了虑省,這時銀行再統(tǒng)計發(fā)現(xiàn)帳戶為600元了,造成虛讀同樣會使銀行不知所措僧凰,到底以哪個為準探颈。
      1. 虛讀與不可重復(fù)讀的區(qū)別:不可重復(fù)讀讀取update的數(shù)據(jù) ,虛讀讀取insert的數(shù)據(jù)训措。
  2. 數(shù)據(jù)庫共定義了四種隔離級別:

    • Serializable : 可解決 臟讀伪节、不可重復(fù)讀、虛讀情況的發(fā)生绩鸣。串行處理 ---解決三類問題

    • Repeatable read :可以解決 不可重復(fù)讀怀大、臟讀,會發(fā)生虛讀 ------MySQL 默認級別

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

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

    Serializable > Repeatable read > read committed > read uncommitted

    數(shù)據(jù)庫隔離問題危害:臟讀> 不可重復(fù)讀 > 虛讀捡多,

    安全級別越高蓖康,處理效率越低;安全級別越低垒手,效率越高蒜焊。所以一般數(shù)據(jù)庫把默認級別定位中間的兩級。

  3. 有關(guān)SQL語句:

    • set transaction isolation level XXX; 設(shè)置事務(wù)隔離級別

    • select @@tx_isolation; 查詢當前事務(wù)隔離級別

  4. JDBC控制數(shù)據(jù)庫隔離級別:Connection類

     Connection setTransactionIsolation(int level);
    

    level字段摘要:

    • static int TRANSACTION_NONE 指示事務(wù)不受支持的常量淫奔。
    • static int TRANSACTION_READ_COMMITTED 指示不可以發(fā)生臟讀的常量山涡;不可重復(fù)讀和虛讀可以發(fā)生。
    • static int TRANSACTION_READ_UNCOMMITTED 指示可以發(fā)生臟讀 (dirty read)唆迁、不可重復(fù)讀和虛讀 (phantom read) 的常量鸭丛。
    • static int TRANSACTION_REPEATABLE_READ 指示不可以發(fā)生臟讀和不可重復(fù)讀的常量;虛讀可以發(fā)生唐责。
    • static int TRANSACTION_SERIALIZABLE 指示不可以發(fā)生臟讀鳞溉、不可重復(fù)讀和虛讀的常量。

6鼠哥、事務(wù)的丟失更新問題(lost update)

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


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

    在mysql中默認情況下,在事務(wù)中當你修改數(shù)據(jù)于颖,自動為數(shù)據(jù)加鎖呆贿,防止兩個事務(wù)同時修改數(shù)據(jù) ---- 即讀鎖

    事務(wù)和鎖和不可分開的,鎖一定是在事務(wù)中使用,當事務(wù)關(guān)閉鎖自動釋放做入。

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

      • select * from account lock in share mode;

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

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

      • select * from account for update ;

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

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

    • 悲觀鎖( Pessimistic Locking )

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

  2. 悲觀鎖原理:使用數(shù)據(jù)庫內(nèi)部鎖機制违帆,進行table的鎖定浙巫,在A修改數(shù)據(jù)時,A就將數(shù)據(jù)鎖定刷后,B此時無法進行修改 ----- 無法發(fā)生兩個事務(wù)同時修改

    由1的畴、2點可知,悲觀鎖可以使用排它鎖實現(xiàn)尝胆。

    為什么叫悲觀鎖丧裁?

    因為假設(shè)丟失更新會發(fā)生,所以是悲觀的含衔。

  3. 樂觀鎖原理:使用的不是數(shù)據(jù)庫鎖機制煎娇,而是一個特殊時間戳標記字段,假設(shè)兩個事務(wù)查詢了同一個記錄贪染,A事務(wù)修改了該記錄缓呛,則時間戳字段會更新,若B事務(wù)再修改該記錄杭隙,此時把之前查詢的時間戳字段與當前時間戳字段比較哟绊,得知數(shù)據(jù)是否發(fā)生了并發(fā)訪問。

    表額外增加一個timestamp類型的字段痰憎,即時間戳字段票髓,該字段在新增數(shù)據(jù)時可設(shè)為null,然后會自動生成當前時間铣耘,同時在修改該記錄時洽沟,也會自動更新為當前時間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜗细,一起剝皮案震驚了整個濱河市玲躯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖跷车,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異橱野,居然都是意外死亡朽缴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門水援,熙熙樓的掌柜王于貴愁眉苦臉地迎上來密强,“玉大人,你說我怎么就攤上這事蜗元』虿常” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵奕扣,是天一觀的道長薪鹦。 經(jīng)常有香客問我,道長惯豆,這世上最難降的妖魔是什么池磁? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮楷兽,結(jié)果婚禮上地熄,老公的妹妹穿的比我還像新娘。我一直安慰自己芯杀,他們只是感情好端考,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揭厚,像睡著了一般却特。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棋弥,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天核偿,我揣著相機與錄音,去河邊找鬼顽染。 笑死漾岳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的粉寞。 我是一名探鬼主播尼荆,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唧垦!你這毒婦竟也來了捅儒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巧还,沒想到半個月后鞭莽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡麸祷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年澎怒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阶牍。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡喷面,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出走孽,到底是詐尸還是另有隱情惧辈,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布磕瓷,位于F島的核電站盒齿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏生宛。R本人自食惡果不足惜县昂,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陷舅。 院中可真熱鬧倒彰,春花似錦、人聲如沸莱睁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仰剿。三九已至创淡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間南吮,已是汗流浹背琳彩。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留部凑,地道東北人露乏。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像涂邀,于是被迫代替她去往敵國和親瘟仿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容