1、事務
(1)事務的概念
事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功。
例如:A——B轉帳,對應於如下兩條sql語句
update account set money=money-100 where name=‘a’;
update account set money=money+100 where name=‘b’;
數據庫默認事務是自動提交的,也就是發一條sql它就執行一條。如果想多條sql放在一個事務中執行,則需要使用如下語句。
(2)數據庫開啓事務命令
方式一:利用SQL語句管理事務
start transaction;--開啓事務,這條語句之後的sql語句將處在一個事務當中,這些sql語句並不會立即執行
Commit--提交事務,一旦提交事務,事務中的所有sql語句纔會執行。
Rollback -- 回滾事務,將之前所有的sql取消。
方式二:在數據庫中存在一個自動提交變量,通過show variables like '%commit%'-----autocommit 值是on,說明開啓了事務自動提交。
可以 set autocommint = off(set autocommint=0),關閉自動提交,此時輸入的sql語句就不會自動提交了,需要手動roolback或commit
2、使用事務
(1)當Jdbc程序向數據庫獲得一個Connection對象時,默認情況下這個Connection對象會自動向數據庫提交在它上面發送的SQL語句。若想關閉這種默認提交方式,讓多條SQL在一個事務中執行,可使用下列語句:
(2)JDBC控制事務語句
Connection.setAutoCommit(false); // 相當於start transaction
Connection.rollback(); rollback
Connection.commit(); commit
3、演示銀行轉帳案例
(1)在JDBC代碼中使如下轉帳操作在同一事務中執行。
update from account set money=money-100 where name=‘a’;
update from account set money=money+100 where name=‘b’;
(2)設置事務回滾點
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit(); //回滾後必須要提交
package com.itheima.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import org.junit.Test;
import com.itheima.util.DaoUtil;
public class Demo1 {
@Test
public void test1(){
Connection conn = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
Savepoint sp = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///day11", "root", "root");
conn.setAutoCommit(false);
ps1 = conn.prepareStatement("update account set money = money+100 where name=?");
ps1.setString(1, "b");
ps1.executeUpdate();
//int i = 1/0;
ps2 = conn.prepareStatement("update account set money = money-100 where name=?");
ps2.setString(1, "a");
ps2.executeUpdate();
sp = conn.setSavepoint();
//-----------------------------------
ps1 = conn.prepareStatement("update account set money = money+100 where name=?");
ps1.setString(1, "b");
ps1.executeUpdate();
int i = 1/0;
ps2 = conn.prepareStatement("update account set money = money-100 where name=?");
ps2.setString(1, "a");
ps2.executeUpdate();
conn.commit();
}catch (Exception e) {
e.printStackTrace();
try {
if(sp !=null){
conn.rollback(sp);
conn.commit();
}else{
conn.rollback();
}
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally{
DaoUtil.close(conn, ps1, null);
DaoUtil.close(conn, ps2, null);
}
}
}
4、事務的特性(ACID)
(1)原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
(2)一致性(Consistency)
事務前後數據的完整性必須保持一致。
(3)隔離性(Isolation)
事務的隔離性是指多個用戶併發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所幹擾,多個併發事務之間數據要相互隔離。
(4)持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響
5、事務的隔離級別
(1)多個線程開啓各自事務操作數據庫中數據時,數據庫系統要負責隔離操作,以保證各個線程在獲取數據時的準確性。
(2)如果不考慮隔離性,可能會引發如下問題:
髒讀(dirty reads)
一個事務讀取了另一個未提交的並行事務寫的數據。
不可重複讀(non-repeatable reads)
一個事務重新讀取前面讀取過的數據, 發現該數據已經被另一個已提交的事務修改過。
幻讀(phantom read)
一個事務重新執行一個查詢,返回一套符合查詢條件的行, 發現這些行因爲其他最近提交的事務而發生了改變。
6、事務的隔離性
(1)髒讀:
指一個事務讀取了另外一個事務未提交的數據。
這是非常危險的,假設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 1000
b 1000
a:
start transaction;
update account set money=money-100 where name='a';
update account set money=money+100 where name='b';
b:
start transaction;
select * from account where name='b';
a:
rollback;
b:
strat transaction;
select * from account where name='b';
(2)不可重複讀:
在一個事務內讀取表中的某一行數據,多次讀取結果不同。
例如銀行想查詢A帳戶餘額,第一次查詢A帳戶爲200元,此時A向帳戶存了100元並提交了,銀行接着又進行了一次查詢,此時A帳戶爲300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。
和髒讀的區別是,髒讀是讀取前一事務未提交的髒數據,不可重複讀是重新讀取了前一事務已提交的數據。
很多人認爲這種情況就對了,無須困惑,當然是後面的爲準。我們可以考慮這樣一種情況,比如銀行程序需要將查詢結果分別輸出到電腦屏幕和寫到文件中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,導致文件和屏幕中的結果不一致,銀行工作人員就不知道以哪個爲準了。
a 1000
b 1000
start transaction;
select sum(money) from account; ---- 總存款:2000
select count(*) from account; ---- 總賬戶數:2
-------------------------
b:
start transaction;
update account set money = money-1000 where name='b';
commit;
-------------------------
select avg(money) from account; ---- 賬戶平均金額:500
(3)虛讀(幻讀)
是指在一個事務內讀取到了別的事務插入的數據,導致前後讀取不一致。
如丙存款100元未提交,這時銀行做報表統計account表中所有用戶的總額爲500元,然後丙提交了,這時銀行再統計發現帳戶爲600元了,造成虛讀同樣會使銀行不知所措,到底以哪個爲準。
a 1000
b 1000
c 1000
start transaction;
select sum(money) from account; ---- 總存款:2000
-------------------------
c:
start transaction;
insert into account values(null,'c',1000);
commit;
-------------------------
select count(*) from account; ---- 總賬戶數:3
7、事務隔離性的設置語句
數據庫共定義了四種隔離級別:
Serializable:可避免髒讀、不可重複讀、虛讀情況的發生。(串行化)
Repeatable read:可避免髒讀、不可重複讀情況的發生。(可重複讀)不可以避免虛讀
Read committed:可避免髒讀情況發生(讀已提交)
Read uncommitted:最低級別,以上情況均無法保證。(讀未提交)
set [global/session] transaction isolation level 設置事務隔離級別
select @@tx_isolation查詢當前事務隔離級別
安全性來說:Serializable>Repeatable read>Read committed>Read uncommitted
效率來說:Serializable<Repeatable read<Read committed<Read uncommitted
通常來說,一般的應用都會選擇Repeatable read或Read committed作爲數據庫隔離級別來使用。
mysql默認的數據庫隔離級別爲:REPEATABLE-READ
如何查詢當前數據庫的隔離級別?select @@tx_isolation;
如何設置當前數據庫的隔離級別?set [global/session] transaction isolation level ...;
~此種方式設置的隔離級別只對當前連接起作用。
set transaction isolation level read uncommitted;
set session transaction isolation level read uncommitted;
~此種方式設置的隔離級別是設置數據庫默認的隔離級別
set global transaction isolation level read uncommitted;
8、事務的丟失更新問題(lost update )
(1)兩個或多個事務更新同一行,但這些事務彼此之間都不知道其它事務進行的修改,因此第二個更改覆蓋了第一個修改
(2)共享鎖:共享鎖和共享鎖可以共存。共享鎖和排他鎖不能共存。在Serializable隔離級別下一個事務進行查詢操作將會加上共享鎖。
(3)排他鎖:排他鎖和所有鎖都不能共存。無論什麼隔離級別執行增刪改操作時,會加上排他鎖
(4).數據庫設計爲Serializable隔離級別,就可以防止更新丟失問題。
樂觀鎖和悲觀鎖並不是數據庫中真實存在的鎖,是我們如何利用共享和排他鎖解決更新丟失問題的兩種解決方案,體現了人們看待事務的態度:
悲觀鎖:悲觀的認爲大部分情況下進行操作都會出現更新丟失問題。
在每次進行查詢的時候,都手動的加上一個排他鎖。
select * from table lock in share mode(讀鎖、共享鎖)
select * from table for update (寫鎖、排它鎖)
樂觀鎖:樂觀的認爲大部分的情況下都不會有更新丟失問題。通過時間戳字段,
在表中設計一個版本字段version,當每次對數據庫中的數據進行修改操作時,版本號都要進行增加。
(5)如果我的程序修改比較少查詢比較多:樂觀鎖
如果我的程序查詢比較少修改比較多:悲觀鎖