一、事務隔離機制_悲觀鎖_樂觀鎖
1.事務特性:ACID
2.常見問題事務:
(1)第一類丢失更新(lost update)
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 查詢餘額為1000 | |
5 | 存入100變為1100 | |
6 | 送出事務 | |
7 | 取出100變為900 | |
8 | 撤銷事務復原 | |
9 | 餘額最後為1000(丢失了B事務) |
(2)髒讀(dirty read)
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 存入100變為1100 | |
5 | 查詢餘額為1100(髒資料) | |
6 | 撤銷事務復原(餘額為1000) | |
7 | 取款1100 | |
8 | 送出事務失敗 |
(3)不可重複讀(non-repeatable read)
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 存入100變為1100 | |
5 | 送出事務(餘額為1100) | |
6 | 查詢餘額為1100 | |
7 | 送出事務 | |
8 |
(4)不可重複讀特殊情況:第二類丢失更新(second lost update)
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 查詢餘額為1000 | |
5 | 取款100變為900 | |
6 | 送出事務 | |
7 | 存入100變為1100 | |
8 | 送出事務 | |
9 | 餘額為1100丢失了A事務 |
(5)幻讀(phantom read)幻讀
時間 | 查詢學生事務A | 新增學生事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢學生為10人 | |
4 | 添加一名學生 | |
5 | 查詢學生為11人 | |
6 | 送出事務 | |
7 | 送出事務 | |
8 |
3.資料庫的事務隔離級别有4種:
Field Summary
Modifier and Type | Field and Description |
---|---|
| A constant indicating that transactions are not supported. |
| A constant indicating that dirty reads are prevented; non-repeatable reads and phantom reads can occur. |
| A constant indicating that dirty reads, non-repeatable reads and phantom reads can occur. |
| A constant indicating that dirty reads and non-repeatable reads are prevented; phantom reads can occur. |
| A constant indicating that dirty reads, non-repeatable reads and phantom reads are prevented. |
TRANSACTION_NONE:沒有,不設定隔離機制
1TRANSACTION_READ_UNCOMMITTED:可以讀取沒有送出時更改的資料,會出現髒讀、不可重複讀、幻讀等問題
2TRANSACTION_READ_COMMITTED:隻能讀事務送出以後的資料,會出現不可重複讀、幻讀問題
4TRANSACTION_REPEATABLE_READ:讀資料的時候給這條語句加一把鎖,别人可以讀但是不能改,會出現幻讀問題
8TRANSACTION_SERIALIZABLE:序列化,串行,必須等我都操作完以後,别人才能操作,排着隊運作,解決所有問題
事務級别越高效率越低,但是越安全;級别是(1,2,4,8)為什麼不是1,2,3,4?因為換算成二進制後1,2,4,8為(0001,0010,0100,1000)這樣用移位運算效率要高,但是在JAVA中不要求這種級别的效率,在C和C++中會要求這麼寫。
4.hibernate事務隔離級别:
一般hibernate設定事務隔離級别為2:hibernate.connection.isolation=2 2級别的事務隔離級别會出現不可重複讀和幻讀等問題。
其中不可重複讀的問題可以通過悲觀鎖和樂觀鎖解決,幻讀問題暫時不考慮。
悲觀鎖:
import javax.persistence.*;
@Entity
public class Account {
private int id;
private int balance;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Modeltest {
private static SessionFactory sf=null;
@BeforeClass
public static void beforeClass(){
sf=new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void testSave(){
Session session=sf.getCurrentSession();
session.beginTransaction();
Account a=new Account();
a.setBalance(100);
session.save(a);
session.getTransaction().commit();
}
@Test
public void testPessimistilock(){
Session session=sf.getCurrentSession();
session.beginTransaction();
/*在取出資料的時候給這條資料加一把鎖,别人不能修改
* load中的lock有以下幾種
* NONE:無鎖機制,事務結束時,自動切換到none
* (如果緩存中存在對象,直接傳回該對象的引用,否則通過select語句到資料庫中加載該對象,預設值.)
* READ:查詢的時候使用,hibernate自動擷取鎖
* (不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象版本一緻)
* WRITE:在insert、update的時候自動擷取鎖
* (儲存對象時會自動使用這種鎖定模式,僅供Hibernate内部使用,應用程式中不應該使用它)
* 以上三種是hibernate内部使用,不用管它
*
*
* FORCE(強制更新資料庫中對象的版本屬性,進而表明目前事務已經更新了這個對象)
* UPGRADE:利用資料庫的 for update 子句加鎖,一般都是寫這種
* (不管緩存中是否存在對象,總是通過select語句到資料庫中加載該對象,如果映射檔案中設定了版本元素,就執行版本檢查,比較緩存中的對象是否和資料庫中對象的版本一緻,如果資料庫系統支援悲觀鎖(如Oracle/MySQL),就執行select...for update語句,如果不支援(如Sybase),執行普通select語句)
* UPGRADE_NOWAIT: Oracle 的特定實作,利用 Oracle 的 for update nowait 子句實作加鎖
* (和LockMode.UPGRADE具有同樣功能,此外,對于Oracle等支援update nowait的資料庫,執行select...for update nowait語句,nowait表明如果執行該select語句的事務不能立即獲得悲觀鎖,那麼不會等待其它事務釋放鎖,而是立刻抛出鎖定異常)
*/
Account a=(Account)session.load(Account.class, 1, LockMode.UPGRADE);
a.setBalance(a.getBalance()-10);
session.getTransaction().commit();
}
@Test
public void testSchemaExport(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
@AfterClass
public static void afterClass(){
sf.close();
}
}
樂觀鎖:在實體裡加一個version版本字段,誰更改一次就加1,如果你在操作的時候對比一下version,一樣的話證明沒人更改。
要在version字段上添加@Version
import javax.persistence.*;
@Entity
public class Account {
private int id;
private int balance;
private int version;
@Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
}
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Modeltest {
private static SessionFactory sf=null;
@BeforeClass
public static void beforeClass(){
sf=new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void testSave(){
//先插入c再插入t保證每個t都關聯不同的c
Session session=sf.getCurrentSession();
session.beginTransaction();
Account a=new Account();
a.setBalance(100);
session.save(a);
session.getTransaction().commit();
}
@Test
public void testOptimisticlock(){
//
Session session=sf.openSession();
Session session1=sf.openSession();
session.beginTransaction();
Account a=(Account)session.load(Account.class, 1);
session1.beginTransaction();
Account a1=(Account)session1.load(Account.class, 1);
a.setBalance(500);
a1.setBalance(1500);
session.getTransaction().commit();//事務送出,會對比一下version都是0,沒人更改過,可以送出,version增加為1
System.out.println(a.getVersion());
session1.getTransaction().commit();//事務送出,對比version發現目前資料庫不是0,則報錯,不做操作。
System.out.println(a1.getVersion());
session.close();
session1.close();
}
@Test
public void testSchemaExport(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
@AfterClass
public static void afterClass(){
sf.close();
}
}
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 存入100變為1100 | |
5 | 查詢餘額為1100(髒資料) | |
6 | 撤銷事務復原(餘額為1000) | |
7 | 取款1100 | |
8 | 送出事務失敗 |
時間 | 取款事務A | 存款事務B |
1 | 事務開始 | |
2 | 事務開始 | |
3 | 查詢餘額為1000 | |
4 | 查詢餘額為1000 | |
5 | 存入100變為1100 | |
6 | 送出事務 | |
7 | 取出100變為900 | |
8 | 撤銷事務復原 | |
9 | 餘額最後為1000(丢失了B事務) |