一、事務 mysql
1.一、事務的基本概念和使用示例sql
數據庫事務,是指做爲單個邏輯工做單元執行的一系列操做,要麼完整地執行,要麼徹底地不執行。 事務處理能夠確保除非事務性單元內的全部操做都成功完成,不然不會永久更新面向數據的資源。經過將一組相關操做組合爲一個要麼所有成功要麼所有失敗的單元,能夠簡化錯誤恢復並使應用程序更加可靠。一個邏輯工做單元要成爲事務,必須知足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。數據庫
JDBC能夠操做Connection的setAutoCommit()方法,給它false參數,提示數據庫啓動事務,在下達一連串的SQL命令後,自行調用Connection的commit()方法,提示數據庫確認(Commit)操做。若是中間發生錯誤,則調用rollback(),提示數據庫撤銷(ROLLBACK)全部執行。同時,若是僅想要撤回某個SQL執行點,則能夠設置存儲點(SAVEPOINT)。一個示範的事務流程以下:安全
Connection conn = ...; Savepoint point = null; try { conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO ..."); ... point = conn.setSavepoint(); stmt.executeUpdate("INSERT INTO ..."); ... conn.commit(); } catch (SQLException e) { e.printStackTrace(); if (conn != null) { try { if (point == null) { conn.rollback(); } else { conn.rollback(point); conn.releaseSavepoint(point); } } catch (SQLException ex) { ex.printStackTrace(); } } } finally { ... if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ex) { ex.printStackTrace(); } } }
須要說明的是,JDBC操做事務的前提條件是數據庫支持事務,若是數據庫自己不支持事務,那麼咱們即便調用setAutoCommit(false)也沒法啓動事務。對於MYSQL來講,MyIsam數據庫引擎不支持事務操做,InnoDB數據庫引擎支持事務操做。 網絡
1.二、隔離級別併發
要理解隔離級別,首先要了解多個事務並行時,可能引起的數據不一致問題有哪些,常見的事務並行引起問題有如下幾類:分佈式
更新遺失函數
更新遺失是指某個事務對字段進行更新的信息,因另外一個事務的介入而遺失更新的效力,一個簡單的示例以下:性能
這個序列就是典型的更新丟失,由於第三步所作的全部修改所有會丟失。若是要避免更新遺失問題,能夠設置隔離級別爲"read uncommitted",這樣A事務已更新但未確認的數據,B事務僅可作讀取操做,但不可作更新操做。這樣上面的四個步驟就是不合法的,必須A事務徹底提交,B事務才能作更新操做。spa
髒讀
"read uncommitted"隔離級別保證了在A事務提交以前,B事務不能作更改操做,可是沒有阻止B事務作讀取操做,可是這個實際上是有問題的:若是A事務更新字段爲"AAA",B事務讀取值爲"AAA"並使用,而後A事務回滾事務,那麼B事務讀取的"AAA"就屬於髒數據。若是要避免髒讀問題,能夠設置隔離級別爲"Read Commited",也就是事務讀取的數據必須是其餘事務已經確認的數據。
不可重複讀
不可重複度是指兩次讀取同一字段的數據不一致,例如:事務A讀取字段爲"AAA",事務B更新數據爲"BBB",事務B提交,事務A讀取字段爲"BBB",事務A連續的兩次讀取,字段值不同。要避免這種問題,能夠設置數據庫隔離級別爲"Repeatable Read",對於這種隔離界別,事務A讀取字段爲"AAA"後,其餘事務在事務A提交前只可讀取該字段,不可更新該字段。
幻讀
幻讀是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,好比這種修改涉及到表中的"所有數據行"。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入"一行新數據"。那麼,之後就會發生操做第一個事務的用戶發現表中仍是有沒有修改的數據行,就好象發生了幻覺同樣。要解決幻讀問題,必須設置隔離級別爲Serializable,Serializable是數據庫隔離級別的最高級別,串行化讀,事務只能一個一個執行,避免了髒讀、不可重複讀、幻讀,可是執行效率慢,須要謹慎使用。
可使用如下命令查詢mysql的隔離級別:
select @@global.tx_isolation,@@tx_isolation;
1.三、悲觀鎖、樂觀鎖
悲觀鎖是採用一種悲觀的態度來對待事務併發問題,認爲系統中的併發更新會很是頻繁,而且事務失敗了之後重來的開銷很大,這樣一來,咱們就須要採用真正意義上的鎖來進行實現。悲觀鎖的實現,每每依靠數據庫提供的鎖機制。悲觀鎖的基本思想就是每次一個事務讀取某一條記錄後,就會把這條記錄鎖住,這樣其它的事務要想更新,必須等之前的事務提交或者回滾解除鎖。
樂觀鎖,顧名思義就是保持一種樂觀的態度,咱們認爲系統中的事務併發更新不會很頻繁,即便衝突了也沒事,大不了從新再來一次。它的基本思想就是每次提交一個事務更新時,咱們先看看要修改的東西從上次讀取之後有沒有被其它事務修改過,若是修改過,那麼更新就會失敗。樂觀鎖的實現方式大可能是基於數據版本 ( Version )記錄機制實現。何謂數據版本?即爲數據增長一個版本標識,在基於數據庫表的版本解決方案中,通常是經過爲數據庫表增長一個 「version」 字段來實現。 讀取出數據時,將此版本號一同讀出,以後更新時,對此版本號加一。此時,將提 交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,若是提交的數據版本號大於數據庫表當前版本號,則予以更新,不然認爲是過時數據。
總的來講,悲觀鎖的機制依賴數據庫的鎖機制,較安全,而樂觀鎖機制經過應用程序控制,性能較好。
1.四、分佈式事務
分佈式事務處理涉及多個分佈在不一樣地方的數據庫,但對數據庫的操做必須所有被提交或者回滾。只要任一數據庫操做時失敗,全部參與事務的數據庫都須要回滾。
Open 組織定義的分佈式事務處理模型X/Open DTP 模型包括應用程序( AP )、事務管理器( TM )、資源管理器( RM ,即數據庫 )、通訊資源管理器( CRM )四部分。而 XA 是 X/Open DTP 定義的事務管理器與數據庫之間的接口規範(即接口函數),事務管理器用它來通知數據庫事務的開始、結束以及提交、回滾等。
XA 接口規範使用兩階段提交協議來完成一個全局事務,保證同一事務中全部數據庫同時成功或者回滾。
兩階段提交協議假設每一個數據庫點都存在一個write-ahead log,每一次的write請求都是先記log後才真正執行寫入。
第一階段爲提交請求階段(Commit-request phase):
1. 事務管理器給全部數據庫發query to commit消息請求,而後開始等待迴應;
2. 數據庫若是能夠提交屬於本身的事務分支,則將本身在該事務分支中所作的操做固定記錄下來(在undo log和redo log中各記一項);
3. 數據庫都回應是否贊成提交的應答。
第二階段爲提交階段(Commit phase):
若是事務管理器收到的全部迴應都是agreement,
1. 事務管理器記日誌並給全部數據庫發commit消息請求;
2. 各個數據庫執行操做,釋放全部該事務相關的鎖和資源;
3. 各個數據庫給事務管理器回覆;
4.當收到全部回覆,事務管理器結束當前事務
若是事務管理器收到的任一回應是abort,
1. 事務管理器記日誌並給全部數據庫發rollback消息請求;
2. 各個數據庫執行undo操做,釋放全部該事務相關的鎖和資源;
3. 各個數據庫給事務管理器回覆;
4.當收到全部回覆,事務管理器結束當前事務
兩階段提交協議的問題在於數據庫在提交請求階段應答後對不少資源處於鎖定狀態,要等到事務管理器收集齊全部數據庫的應答後,才能發commit或者rollback消息結束這種鎖定。鎖定時間的長度是由最慢的一個數據庫制約,若是數據庫一直沒有應答,全部其餘庫也須要無休止的鎖並等待。而且,若是事務管理器出現故障,被鎖定的資源將長時間處於鎖定狀態。不管是任一數據庫或者事務管理器故障,其餘數據庫都須要永久鎖定或者至少長時間鎖定。而且,分佈式系統中節點越多,存在緩慢網絡或者故障節點的機率也就越大,資源被長時間鎖定的機率指數上升。
兩階段提交協議的另外一個問題是隻要有任意一個數據庫不可用都會致使事務失敗,這致使事務更傾向於失敗。對於多個副本的備份系統,不少時候咱們但願部分副本點失效時系統仍然可用,使用該協議則不能實現。而且,分佈式系統中節點越多,存在故障節點的機率也就越大,系統的可用性指數降低。
另外,若是數據庫在第一階段應答後到第二階段正式提交前的某個階段網絡故障或者節點故障,該協議沒法提交或回滾,數據不一致不能絕對避免。
二、存儲過程
JDBC能夠調用存儲過程,要調用存儲過程,首先咱們應該建立存儲過程:
//建立表,並插入數據 create table g(num int,value varchar(10)); insert into g values(1, '1'),(10, '10'),(60, '60'),(100, '100'); //建立存儲過程 DELIMITER $ CREATE PROCEDURE p1(IN n int, OUT avg double, OUT min int) BEGIN select avg(num) from g where num > n INTO avg; select min(num) from g where num > n INTO min; END$ DELIMITER ;
JDBC調用存儲過程的接口與增刪改查的不一樣,JDBC調用存儲過程應該使用CallableStatement,簡單示例以下:
private static void ps() throws SQLException{ Connection conn = null; CallableStatement cs = null; try{ conn = JdbcUtils.getConnection(); cs = conn.prepareCall("call p1(?,?,?)"); cs.registerOutParameter(2, Types.DOUBLE);//設置out參數 cs.registerOutParameter(3, Types.INTEGER);//設置out參數 cs.setInt(1, 18);//設置in參數 cs.executeUpdate(); System.out.println(cs.getInt(2) + " " + cs.getInt(3)); } finally{ JdbcUtils.free(null, cs, conn); } }
三、批次更新
若是須要對對數據庫進行大量數據更新,使用循環屢次操做更新是比較浪費性能的,對於這種場景,咱們可使用addBatch()方法來收集SQL,並使用executeBatch()方法將收集的SQL批次更新,例如:
Statement stmt = conn.createStatement(); while(someCondition) { stmt.addBatch("INSERT INTO ..."); } stmt.executeBatch();