微信公衆號【黃小斜】大廠程序員,互聯網行業新知,終身學習踐行者。關注後回覆「Java」、「Python」、「C++」、「大數據」、「機器學習」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「筆試」、「面試」、「面經」、「計算機基礎」、「LeetCode」 等關鍵字能夠獲取對應的免費學習資料。 前端
事務指邏輯上的一組操做,組成這組操做的各個單元,要不所有成功,要不所有不成功。
例如:A向B轉帳100元,對應於以下兩條sql語句:java
update from account set money=money+100 where name='b'; update from account set money=money-100 where name='a';
數據庫默認事務是自動提交的,也就是發一條sql它就執行一條,若是想多條sql放在一個事務中執行,則須要使用以下語句:mysql
start transaction … … commit
數據庫開啓事務命令:程序員
start transaction
:開啓事務rollback
:回滾事務commit
:提交事務編寫測試SQL腳本,以下:面試
/* 建立數據庫 */ create database day16; use day16; /* 建立帳戶表 */ create table account ( id int primary key auto_increment, name varchar(40), money float ) character set utf8 collate utf8_general_ci; /* 插入測試數據 */ insert into account(name,money) values('aaa',1000); insert into account(name,money) values('bbb',1000); insert into account(name,money) values('ccc',1000);
下面咱們在MySQL數據庫中模擬aaa向bbb轉賬這個業務場景。算法
開啓事務(start transaction)
使用」start transaction」開啓MySQL數據庫的事務,以下所示:
咱們首先在數據庫中模擬轉帳失敗的場景,首先執行update語句讓aaa用戶的money減小100塊錢,以下圖所示:
如今假設程序拋出異常,也即該連接斷了,代碼塊沒有完成,此時數據庫會自動回滾掉此sql語句形成的影響,也就是說這條sql語句沒有執行。咱們如今就來模擬這種狀況,咱們關閉當前操做的dos命令行窗口,這樣就致使了剛纔執行的update語句的數據庫的事務沒有被提交,那麼咱們對aaa用戶的修改就不算是真正的修改了,下次在查詢aaa用戶的money時,依然仍是以前的1000,以下圖所示:
sql
提交事務(commit)
下面咱們在數據庫模擬aaa向bbb轉帳成功的場景。
咱們手動提交(commit)數據庫事務以後,aaa向bbb轉帳100塊錢的這個業務操做算是真正成功了,aaa帳戶中少了100,bbb帳戶中多了100。數據庫
回滾事務(rollback)
經過手動回滾事務,讓全部的操做都失效,這樣數據就會回到最初的初始狀態!微信
當Jdbc程序向數據庫得到一個Connection對象時,默認狀況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可以使用下列的JDBC控制事務語句:markdown
在JDBC代碼中演示銀行轉賬案例,使以下轉賬操做在同一事務中執行:
update from account set money=money-100 where name=‘aaa’; update from account set money=money+100 where name=‘bbb’;
模擬aaa向bbb轉帳成功時的業務場景
public class Demo1 { /* * a--->b轉100元 */ public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); // 至關於start transaction,開啓事務 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; st = conn.prepareStatement(sql1); st.executeUpdate(); st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit(); } finally { JdbcUtils.release(conn, st, rs); } } }
模擬aaa向bbb轉帳過程當中出現異常致使有一部分SQL執行失敗後讓數據庫自動回滾事務
public class Demo1 { /* * a--->b轉100元 */ public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); // 至關於start transaction,開啓事務 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; st = conn.prepareStatement(sql1); st.executeUpdate(); int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令 st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit(); } finally { JdbcUtils.release(conn, st, rs); } } }
模擬aaa向bbb轉帳過程當中出現異常致使有一部分SQL執行失敗時手動通知數據庫回滾事務
public class Demo1 { /* * a--->b轉100元 */ public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); // 至關於start transaction,開啓事務 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; st = conn.prepareStatement(sql1); st.executeUpdate(); int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令 st = conn.prepareStatement(sql2); st.executeUpdate(); conn.commit(); } catch (Exception e) { e.printStackTrace(); conn.rollback(); // 捕獲到異常以後手動通知數據庫執行回滾事務的操做 } finally { JdbcUtils.release(conn, st, rs); } } }
在開發中,有時候可能須要手動設置事務的回滾點,在JDBC中使用以下的語句設置事務回滾點:
Savepoint sp = conn.setSavepoint(); Conn.rollback(sp); Conn.commit(); // 回滾後必須通知數據庫提交事務
設置事務回滾點範例:
public class Demo2 { // 事務回滾點概念 public static void main(String[] args) throws SQLException { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try { conn = JdbcUtils.getConnection(); // MySQL默認的隔離級別——REPEATABLE-READ,而且是嚴格遵循數據庫規範設計的,即支持4種隔離級別 // Oracle默認的隔離級別——Read committed,而且不支持這4種隔離級別,只支持這4種隔離級別中的2種,Read committed和Serializable // conn.setTransactionIsolation(); // 至關於設置CMD窗口的隔離級別 conn.setAutoCommit(false); // 至關於start transaction,開啓事務 // 不符合實際需求 String sql1 = "update account set money=money-100 where name='aaa'"; String sql2 = "update account set money=money+100 where name='bbb'"; String sql3 = "update account set money=money+100 where name='ccc'"; st = conn.prepareStatement(sql1); st.executeUpdate(); /* * 只但願回滾掉這一條sql語句,上面那條sql語句讓其執行成功 * 這時可設置事務回滾點 */ sp = conn.setSavepoint(); st = conn.prepareStatement(sql2); st.executeUpdate(); int x = 1/0; // 程序運行到這個地方拋異常,後面的代碼就不執行,數據庫沒有收到commit命令 st = conn.prepareStatement(sql3); st.executeUpdate(); conn.commit(); } catch (Exception e) { e.printStackTrace(); conn.rollback(sp); // 回滾到sp點,sp點上面的sql語句發給數據庫執行,因爲數據庫沒收到commit命令,數據庫又會自動將這條sql語句的影響回滾掉,因此回滾完,必定要記得commit命令。 conn.commit(); // 手動回滾後,必定要記得提交事務 } finally { JdbcUtils.release(conn, st, rs); } } }
事務的四大特性中最麻煩的是隔離性,下面重點介紹一下事務的隔離級別。
多個線程開啓各自事務操做數據庫中數據時,數據庫系統要負責隔離操做,以保證各個線程在獲取數據時的準確性。
若是事務不考慮隔離性,可能會引起以下問題:
髒讀
指一個事務讀取了另一個事務未提交的數據。
這是很是危險的,假設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就會損失100元。
數據庫共定義了四種隔離級別,應用《高性能mysql》一書中有說明:
總結:在MySQL中,實現了這四種隔離級別,分別有可能產生問題以下所示:
下面說說修改事務隔離級別的方法:
全局修改,修改my.ini(或mysql.ini)配置文件,在最後加上
#可選參數有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE. [mysqld] transaction-isolation = REPEATABLE-READ
注意:MySQL默認的隔離級別爲REPEATABLE-READ,而且是嚴格遵循數據庫規範設計的,即支持4種隔離級別;Oracle默認的隔離級別爲Read committed,而且不支持這4種隔離級別,只支持這4種隔離級別中的2種,Read committed和Serializable。
對當前session修改,在登陸mysql客戶端後,執行命令:
set session transaction isolation level read uncommitted; // 設置當前事務隔離級別
注意:session是不能掉的,否則你設置不會成功,MySQL的隔離級別仍是默認的隔離級別——REPEATABLE-READ,以下所示:
查詢當前事務隔離級:
select @@tx_isolation; // 查詢當前事務隔離級別
下面,將利用MySQL的客戶端程序,分別測試幾種隔離級別。測試數據庫爲day16,表爲account;表以下:
兩個命令行客戶端分別爲a(黑色背景窗口),b(藍色背景窗口);不斷改變b的隔離級別,在a端修改數據。
下面,將利用Java程序來測試Serializable隔離級別。
public class Demo3 { public static void main(String[] args) throws SQLException, InterruptedException { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try { conn = JdbcUtils.getConnection(); conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // 至關於設置CMD窗口的隔離級別 conn.setAutoCommit(false); String sql = "select * from account"; conn.prepareStatement(sql).executeQuery(); // 故意讓程序睡眠20秒,睡眠20秒以後事務才結束,程序運行完 Thread.sleep(1000*20); conn.commit(); } finally { JdbcUtils.release(conn, st, rs); } } }
程序運行,同時在客戶端開啓一個事務,插入一條記錄,須要等待一段時間才能插入進去。