本文包括:
1怀薛、事務(wù)概念
2刺彩、MySQL管理事務(wù)
3、JDBC控制事務(wù)進程
4枝恋、事務(wù)的特性(ACID)
5创倔、事務(wù)的隔離級別
6、事務(wù)的丟失更新問題(lost update)
1焚碌、事務(wù)的概念
-
事務(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’;
-
自動提交
MySQL 數(shù)據(jù)庫 默認情況下 一條SQL就是一個單獨事務(wù),事務(wù)是自動提交的
Oracle 數(shù)據(jù)庫 默認情況下 事務(wù)不是自動提交 台盯,所有SQL都將處于一個事務(wù)中罢绽,你需要手動進行commit提交/rollback回滾
2、MySQL管理事務(wù)
在事務(wù)管理中執(zhí)行sql静盅,使用數(shù)據(jù)庫內(nèi)臨時表保存良价,在沒有進行事務(wù)提交或者回滾,其它用戶無法看到事務(wù)操作結(jié)果的
SQL語言中只有DML才能被事務(wù)管理蒿叠,如insert update delete
-
SQL語句:
start transaction 開啟事務(wù) (所有對數(shù)據(jù)表增加明垢、修改、刪除操作 臨時表進行)
rollback 回滾事務(wù) (取消剛剛操作)
commit 提交事務(wù) (確認剛才操作)
savepoint my_savepoint 保存點(回滾點市咽,像打游戲時的存檔)
rollback to savepoint my_savepoint 回滾到指定的保存點
3痊银、JDBC控制事務(wù)進程
當Jdbc程序向數(shù)據(jù)庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數(shù)據(jù)庫提交在它上面發(fā)送的SQL語句魂务。若想關(guān)閉這種默認提交方式曼验,讓多條SQL在一個事務(wù)中執(zhí)行。
-
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
-
注意:
使用保存點進行事務(wù)控制時粘姜,回滾到指定保存點之后鬓照,必須要調(diào)用commit。
如果直接使用沒有保存點的rollback命令孤紧,那么事務(wù)中的所有保存點都將被忽略豺裆,并且撤銷整個事務(wù)(這就是普通rollback之后不需要調(diào)用commit的原因)
-
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ù)的隔離級別
-
不考慮事務(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é)果不同(有時并不一定是壞問題)亭螟。
- 例如銀行想查詢A帳戶余額挡鞍,第一次查詢A帳戶為200元,此時A向帳戶存了100元并提交了预烙,銀行接著又進行了一次查詢墨微,此時A帳戶為300元了。銀行兩次查詢不一致扁掸,可能就會很困惑欢嘿,不知道哪次查詢是準的。
- 不可重復(fù)讀與臟讀的區(qū)別是:臟讀是讀取前一事務(wù)未提交的臟數(shù)據(jù)也糊,不可重復(fù)讀是重新讀取了前一事務(wù)已提交的數(shù)據(jù)。
-
虛讀:一個事務(wù)讀取另一個事務(wù)插入數(shù)據(jù)羡宙,造成在一個事務(wù)中兩次讀取記錄條數(shù)不同(比不可重復(fù)讀危害性更低狸剃,很多數(shù)據(jù)庫不認為虛讀是問題)。
- 如丙存款100元未提交狗热,這時銀行做報表統(tǒng)計account表中所有用戶的總額為500元钞馁,然后丙提交了虑省,這時銀行再統(tǒng)計發(fā)現(xiàn)帳戶為600元了,造成虛讀同樣會使銀行不知所措僧凰,到底以哪個為準探颈。
- 虛讀與不可重復(fù)讀的區(qū)別:不可重復(fù)讀讀取update的數(shù)據(jù) ,虛讀讀取insert的數(shù)據(jù)训措。
-
-
數(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ù)庫把默認級別定位中間的兩級。
-
有關(guān)SQL語句:
set transaction isolation level XXX; 設(shè)置事務(wù)隔離級別
select @@tx_isolation; 查詢當前事務(wù)隔離級別
-
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)
-
丟失更新問題描述:兩個事務(wù)查詢同一行熟菲,再先后更新這一行,但這些事務(wù)彼此之間都不知道其它事務(wù)進行的修改朴恳,所以因此第二個更改覆蓋了第一個修改抄罕。
-
首先介紹:在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 ;
如果一張表想添加排它鎖蒋情,則之前表一定沒有加過共享鎖和排他鎖,否則無法添加排它鎖(排他鎖和其它共享鎖秫逝、排它鎖都具有互斥效果)
-
-
丟失更新問題的解決有兩種方法:
-
悲觀鎖( Pessimistic Locking )
select * from table lock in share mode(讀鎖恕出、共享鎖) select * from table for update (寫鎖、排它鎖)
樂觀鎖( Optimistic Locking )
通過時間戳字段
-
-
悲觀鎖原理:使用數(shù)據(jù)庫內(nèi)部鎖機制违帆,進行table的鎖定浙巫,在A修改數(shù)據(jù)時,A就將數(shù)據(jù)鎖定刷后,B此時無法進行修改 ----- 無法發(fā)生兩個事務(wù)同時修改
由1的畴、2點可知,悲觀鎖可以使用排它鎖實現(xiàn)尝胆。
為什么叫悲觀鎖丧裁?
因為假設(shè)丟失更新會發(fā)生,所以是悲觀的含衔。
-
樂觀鎖原理:使用的不是數(shù)據(jù)庫鎖機制煎娇,而是一個特殊時間戳標記字段,假設(shè)兩個事務(wù)查詢了同一個記錄贪染,A事務(wù)修改了該記錄缓呛,則時間戳字段會更新,若B事務(wù)再修改該記錄杭隙,此時把之前查詢的時間戳字段與當前時間戳字段比較哟绊,得知數(shù)據(jù)是否發(fā)生了并發(fā)訪問。
表額外增加一個timestamp類型的字段痰憎,即時間戳字段票髓,該字段在新增數(shù)據(jù)時可設(shè)為null,然后會自動生成當前時間铣耘,同時在修改該記錄時洽沟,也會自動更新為當前時間。