MySQL資料庫事務略知一二
事務的概念
事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
例如:A向B轉賬100元,對應於如下兩條sql語句:
update from account set money=money+100 where name='b';
update from account set money=money-100 where name='a';
資料庫預設事務是自動提交的,也就是發一條sql它就執行一條,如果想多條sql放在一個事務中執行,則需要使用如下語句:
start transaction
…
…
commit
資料庫開啟事務命令:
start transaction
rollback
:回滾事務commit
:提交事務
MySQL資料庫中操作事務命令
編寫測試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,如下圖所示:
提交事務(commit)
下面我們在資料庫模擬aaa向bbb轉賬成功的場景。
我們手動提交(commit)資料庫事務之後,aaa向bbb轉賬100塊錢的這個業務操作算是真正成功了,aaa賬戶中少了100,bbb賬戶中多了100。回滾事務(rollback)
通過手動回滾事務,讓所有的操作都失效,這樣資料就會回到最初的初始狀態!
JDBC中使用事務
當Jdbc程式向資料庫獲得一個Connection物件時,預設情況下這個Connection物件會自動向資料庫提交在它上面傳送的SQL語句。若想關閉這種預設提交方式,讓多條SQL在一個事務中執行,可使用下列的JDBC控制事務語句:
- Connection.setAutoCommit(false); //開啟事務(start transaction)
- Connection.rollback(); //回滾事務(rollback)
- Connection.commit(); //提交事務(commit)
JDBC使用事務範例
在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);
}
}
}
事務的四大特性(ACID)
- 原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要麼全部成功,要麼全部失敗。比如在同一個事務中的SQL語句,要麼全部執行成功,要麼全部執行失敗。 - 一致性(Consistency)
官網上事務一致性的概念是:事務必須使資料庫從一個一致性狀態變換到另外一個一致性狀態。還要一種說法是事務前後資料的完整性必須保持一致。以轉賬為例子,A向B轉賬,假設轉賬之前這兩個使用者的錢加起來總共是2000,那麼A向B轉賬之後,不管這兩個賬戶怎麼轉,A使用者的錢和B使用者的錢加起來的總額還是2000,這個就是事務的一致性。 - 隔離性(Isolation)
事務的隔離性是多個使用者併發訪問資料庫時,資料庫為每一個使用者開啟的事務,不能被其他事務的操作資料所幹擾,多個併發事務之間要相互隔離。 - 永續性(Durability)
永續性是指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響。
事務的隔離級別
事務的四大特性中最麻煩的是隔離性,下面重點介紹一下事務的隔離級別。
多個執行緒開啟各自事務操作資料庫中資料時,資料庫系統要負責隔離操作,以保證各個執行緒在獲取資料時的準確性。
事務不考慮隔離性可能會引發的問題
如果事務不考慮隔離性,可能會引發如下問題:
髒讀
指一個事務讀取了另外一個事務未提交的資料。
這是非常危險的,假設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元。
- 不可重複讀
在一個事務內讀取表中的某一行資料,多次讀取結果不同。(一個事務讀取到了另外一個事務提交的資料)
例如銀行想查詢a帳戶餘額,第一次查詢a帳戶為200元,此時a向帳戶記憶體了100元並提交了,銀行接著又進行了一次查詢,此時a帳戶為300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。可將例子簡化為:讀表中某一行資料,例如a賬戶第一次讀為1000,第二次讀為1100。
不可重複讀和髒讀的區別是,髒讀是讀取前一事務未提交的髒資料,不可重複讀是重新讀取了前一事務已提交的資料。
很多人認為這種情況就對了,無須困惑,當然是以後面的結果為準了。我們可以考慮這樣一種情況,比如銀行程式需要將查詢結果分別輸出到電腦螢幕和寫到檔案中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,導致檔案和螢幕中的結果不一致,銀行工作人員就不知道以哪個為準了。 - 虛讀(幻讀)
虛讀(幻讀)是指在一個事務內讀取到了別的事務插入的資料,導致前後讀取不一致。
如丙存款100元未提交,這時銀行做報表統計account表中所有使用者的總額為500元,然後丙提交了,這時銀行再統計發現帳戶為600元了,造成虛讀同樣會使銀行不知所措,到底以哪個為準。可將例子簡化為:讀整個表,即表的行數,例如第一次讀某個表有3條記錄,第二次讀該表又有4條記錄。
資料庫共定義了四種隔離級別,應用《高效能mysql》一書中有說明:
- Serializable(序列化):可避免髒讀、不可重複讀、虛讀情況的發生。
- Repeatable read(可重複讀):可避免髒讀、不可重複讀情況的發生。
- Read committed(讀已提交):可避免髒讀情況發生。
- Read uncommitted(讀未提交):最低級別,以上情況均無法保證。
總結:在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端修改資料。
- 將b的隔離級別設定為read uncommitted(未提交讀)
在a未更新資料之前,b客戶端
a更新資料,a向b轉帳100元
此時b查詢自己的帳戶,就會發現自己多了100元錢,出現了髒讀(這個事務讀取到了別的事務未提交的資料)
如果a等b走後再回滾
此時b查詢自己的帳戶,發現又少掉了100元錢,兩次讀取的資料不一樣,出現不可重複讀現象
a提交完事務,再開啟一個事務,向表account中新增一條記錄
此時b再次查詢account表,發現表account中多了一條記錄,出現幻讀現象
- 將客戶端b的事務隔離級別設定為read committed(已提交讀)
在a未更新資料之前,b客戶端
a更新資料,a向b轉帳100元
b查詢自己的帳戶,金額沒有發生任何變化,說明已提交讀隔離級別解決了髒讀的問題
a此刻提交事務
b再次查詢自己的帳戶,發現自己又多了100元錢,這時就發生不可重複讀(指這個事務讀取到了別的事務提交的資料)
a再開啟一個事務,向表account中新增一條記錄
然後b再次查詢account表,發現表account中多了一條記錄,出現幻讀現象
- 將b的隔離級別設定為repeatable read(可重複讀)
在a未更新資料之前,b客戶端
a更新資料,a向b轉帳100元
b查詢自己的帳戶,金額沒有發生任何變化,這說明repeatable read這種級別可避免髒讀
a此刻提交事務
b再次查詢自己的帳戶,金額沒有發生任何變化,這說明repeatable read這種級別還可以避免不可重複讀
a再開啟一個事務,向表account中新增一條記錄
然後b再次查詢account表,發現表中可能會多出一條ddd的記錄(也有可能不會多出一條ddd的記錄,我測試時就是這種情況),這就發生了虛讀,也就是在這個事務內讀取了別的事務插入的資料(幻影資料)
- 將b的隔離級別設定為可序列化 (Serializable)
為可序列化 (Serializable)均可避免髒讀、不可重複讀、幻讀。避免髒讀和不可重複讀的情況我就不測試了,測試步驟同上,下面我重點講解可序列化 (Serializable)避免幻讀的情況。
事務b端
事務a端
因為此時事務b的隔離級別設定為serializable,開始事務後,並沒有提交,所以事務a只能等待。
事務b提交事務,事務b端
事務a端
serializable完全鎖定欄位,若一個事務來查詢同一份資料就必須等待,直到前一個事務完成並解除鎖定為止,是完整的隔離級別,會鎖定對應的資料表格,因而會有效率的問題。
結論:Serializable隔離級別,雖然可避免所有問題,但效能、效率是最低的,原因是它採取的是鎖表的方式,即單執行緒的方式,即有一個事務來操作這個表了,另外一個事務只能等在外面進不來。
下面,將利用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);
}
}
}
程式執行,同時在客戶端開啟一個事務,插入一條記錄,需要等待一段時間才能插入進去。
相關推薦
MySQL資料庫事務略知一二
事務的概念 事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。 例如:A向B轉賬100元,對應於如下兩條sql語句: update from account set money=money+100 where name='b
mysql資料庫 事務和索引
1.MySQL資料庫特性: 原子性(atomidity) 一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性 一致性(consistency) 資料庫總是從一個一致性的狀態
遇到mysql資料庫事務隔離級別相關的小坑
幾乎所有軟體工程師都知道,mysql有4種事務隔離級別,但是實際開發過程中可能有時候忽略這個小細節,有時候可能是本來就沒有考慮過,有時候也可能是其他的原因,比如我這次踩到的小坑。 事情還原: 1、需求一:是新建一個商戶,但是客戶要求在建立商戶的時候要預設給他們開
MySQL資料庫事務處理
事務處理用於有效記錄某機構感興趣的業務活動(稱為事務)的資料處理(例如銷售、供貨的定購或貨幣傳輸)。通常,聯機事務處理 (OLTP) 系統執行大量的相對較小的事務。——百度百科 事務處理是將多個操作或者命令一起執行,所有命令全部成功執行才意味著該事務的成功,任何一個命令失敗
MYSQL資料庫事務4種隔離級別及7種傳播行為
事務的特性: 原子性:事務的不可分割,組成事務的各個邏輯單元不可分割。 一致性:事務執行的前後,資料完整性保持一致。 隔離性:事務執行不應該受到其他事務的干擾。 永續性:事務一旦結束,資料就持久化到資料庫中。 檢視/設定隔離級別 檢視:SELECT @@tx
mysql資料庫——事務
為什麼會有事務:為了保證流程的完整執行。 事務是一組不可被分割的SQL語句的集合,是一組連續的資料庫操作, 作為一個單一的工作單元來執行。這一組操作要麼全部成功執行,要麼全部回滾 最經典的就是銀行轉賬,一個轉出,一個收到。 mysql 事務主要用於處理操作量大,複雜度高的資料, 事物處
MySQL資料庫事務例項(模擬銀行轉賬)
在資料庫系列文章中[MySQL資料庫事務基本操作](http://blog.csdn.net/fengpojian/article/details/73571983) 介紹了MySQL資料庫基本的事務操作。這篇文章將介紹一個例項來更好的理解學習MySQL資料庫事
c語言mysql資料庫事務開始、提交、回滾範例
1、 事務提交模式修改:修改資料庫提交模式為0[手動提交] memset ( sql, 0x00, sizeof( sql ) ); memcpy ( sql, "set autocommit=0;"
MySQL資料庫事務隔離級別(Transaction Isolation Level)
今天在學習JDBC的時候看到了關於MySql的事務的隔離級別的問題,感覺內容挺高階的,所以記錄一篇文章,以備後面使用。 資料庫隔離級別有四種,應用《高效能mysql》一書中的說明: 然後說說修改事務隔離級別的方法: 1.全域性修改,修改mysql.in
[MySQL] 生產環境MySQL資料庫事務一直在RUNNING
前言:運營人員反映,有一單子提交卡住了,頁面一直沒有返回。1,剛開始懷疑是應用伺服器或者db壓力過高hang住了,馬上去check應用伺服器以及db的負載,看起來都OK,蠻低的,應該不是DB效能問題。2,最後去看下是否是表鎖住了,檢視到有2個事務一直RUNNING,沒有結束。
MySQL資料庫事務基本操作
這篇日誌是自己學習了MySQL資料庫的事務之後,總結的一點心得體會。都是一些非常基礎的知識點。如能幫助諸位初學者,那真是我的榮幸。 資料庫的事務,很多的部落格也都有了詳細的介紹。最基本的事務特性啊(ACID)這裡就不再贅述。 首先,如果想要開啟MySQL資料庫的
MySql資料庫事務的4大特性
這個問題經常會在一些面試題中被問到。總得來說,資料庫事務的4大特性可以簡稱為ACID。 1、原子性(Atomicity) 事務中包含的程式作為資料庫的邏輯工作單位,它對資料庫中的資料進行操作時,要麼全部執行,要麼都不執行。 舉個例子,你給小A轉賬500塊時。這個時候銀行的資
MySQL資料庫事務的四大特性以及事務的隔離級別
目錄 一、事務的四大特性(ACID) 如果一個數據庫聲稱支援事務的操作,那麼該資料庫必須要具備以下四個特性: 1、原子性(Atomicity) 原子性是指事務包含的所有操作要麼全部成功,要麼全部失敗回滾,因此事務的操作如
mysql資料庫事務型別
資料庫事務有不同的隔離級別,不同的隔離級別對鎖的使用是不同的,鎖的應用最終導致不同事務的隔離級別。隔離性分為四個級別: 1讀未提交:(Read Uncommitted) 2讀已提交(Read Committed) 大多數資料庫預設的隔離級別 3可重複讀(Repeatable-
MySQL 資料庫事務與複製
好久沒有寫技術文章了,因為一直在思考 「後端分散式」這個系列到底怎麼寫才合適。 最近基本想清楚了,「後端分散式」包括「分散式儲存」和 「分散式計算」兩大類。 結合實際工作中碰到的問題,以尋找答案的方式來剖解技術,很多時候我們都不是在創造新技術,
MySQL資料庫事務各隔離級別加鎖情況--read committed && MVCC
浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>
理解MySQL資料庫事務-隔離性
Transaction事務是指一個邏輯單元,執行一系列操作的SQL語句。 事務中一組的SQL語句,要麼全部執行,要麼全部回退。在Oracle資料庫中有個名字,叫做transaction ID 在關係型資料庫中,事務必須ACID的特性。 原子性,事務中的操作,要不全部執行,要不都不執行 一致性,事務完成前後,
MySQL(一):MySQL資料庫事務與鎖
#基本概念 事務是指滿足ACID特性的的一組操作,可以通過Commit提交事務,也可以也可以通過Rollback進行回滾。會存在中間態和一致性狀態(也是真正在資料庫表中存在的狀態) #ACID + **Atomicity【原子性】**:事務被視為不可分割的最小單元,事務的所有操作要麼全部提交成功,要麼全部失敗
SSM框架+MySQL資料庫配置事務管理
資料庫事務(Database Transaction) ,是指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。 例如銀行轉賬,A賬戶轉100元給B賬戶,正常的流程是A賬戶減掉100元,B賬戶增加100元。如果轉賬失敗的話,不能出現A賬戶已經減掉100元而B賬戶沒有增加10
【搞定MySQL資料庫】之事務的4種隔離級別詳解
資料庫事務的隔離級別有4種,由低到高分別為Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事務的併發操作中可能會出現髒讀,不可重複讀,幻讀。下面通過事例一一闡述它們的概念與聯絡。 1、讀未提交