1. 程式人生 > >數據庫事物

數據庫事物

所有 author 它的 mode 回滾 簡單的 evel actor 連接數

DEFAULT:采用數據庫默認隔離級別
READ_UNCOMMITTED:保證了讀取過程中不會讀取到非法數據
READ_COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“臟讀取”。該級別適用於大多數系統。
REPEATABLE_READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重復讀取”的情況,但是帶來了更多的性能損失。
SERIALIZABLE:最嚴格的級別,事務串行執行,資源消耗最大

@Transactional(isolation = Isolation.READ_UNCOMMITTED)讀取未提交數據(會出現臟讀, 不可重復讀) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)讀取已提交數據(會出現不可重復讀和幻讀) 避免臟讀
@Transactional(isolation = Isolation.REPEATABLE_READ)可重復讀(會出現幻讀) 避免了不可重復讀
@Transactional(isolation = Isolation.SERIALIZABLE)串行化


第一類丟失更新(lost update):在完全未隔離事務的情況下,兩個事物更新同一條數據資源,某一事物異常終止,回滾造成第一個完成的更新也同時丟失。
第二類丟失更新(second lost updates):是不可重復讀的特殊情況,如果兩個事務都讀取同一行,然後兩個都進行寫操作,並提交,第一個事務所做的改變就會丟失。
臟讀(dirty read):如果第二個事務查詢到第一個事務還未提交的更新數據,形成臟讀。因為第一個事務你還不知道是否提交,所以數據不一定是正確的。
虛讀(phantom read):一個事務執行兩次查詢,第二次結果集包含第一次中沒有或者某些行已被刪除,造成兩次結果不一致,只是另一個事務在這兩次查詢中間插入或者刪除了數據造成的
不可重復讀(unrepeated read):一個事務兩次讀取同一行數據,結果得到不同狀態結果,如中間正好另一個事務更新了該數據,兩次結果相異,不可信任
幻覺讀:是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麽,以後就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣


orcle 不支持臟讀取

對於不同事務間的鎖定,數據一致性沖突,ms sql server 有四種處理方式
read uncommitted:可以臟讀,當然更可能發生不可重復的讀 select 不產生任何鎖
read committed:不能臟讀,但可以發生不可重復的讀... select 對被選中的行加共享鎖(頁內鎖定行過多會上升為頁鎖),並對表加意圖共享鎖
reapetable read:事務內不允許別的對話update被鎖定的數據 鎖定方式同上,不同的是客戶端得到數據後並不釋放鎖,而要等本事務提交後才釋放
serializable read:事務內不允許別的對話插入數據,更別說update了,這種鎖很厲害,直接一個表級共享鎖,不允許別的對話對表的任何更改

oracle通常只有上述2,4類型(外加一個read only),oracle通常的select並不產生任何鎖,而且保證讀取數據的一致性,不過以我個人的實驗和二者的機理得知在一般情況下oracle的回滾段機制並不比ms sqlserver 回傳行數據給客戶後立即釋放行共享鎖快(特別是delete),有時間以後詳細說說
oracle的這兩種方式的select同樣不產生任何鎖,不同的是第一種select可以看到別的事務已經commit的數據,而第二種需要在本事務提交後才能看到更改後的數據(即使發生在期間的別的事務已提交)



oracle
READ_COMMITTED 同一事務兩次讀取之間,又有事務更改並提交改行,造成兩次讀取不一致
0,兩個事務同時讀取到一條數據,事務A修改提交,事務B再次修改提交,造成修改丟失
1,測試時註意同一事務中兩次查詢字段不要一致,sql緩存
2,mybatis配置文件 useCache="false"(不用二級緩存) flushCache="true"(清空緩存) 防止sql緩存,可測試不可重復讀
SERIALIZABLE 事務A中先查詢,oracle客戶的更改提交改數據(或新增加一條數據),A再做update,報異常
0,兩個事務同時讀取到一條數據,事務A修改提交,事務B再次修改提交,B修改失敗(若事務B不做讀取可修改成功)
1,如使用READ_COMMITTED級別則不會
2,如果不先查詢,也能再次修改成功

mysql
同一事務中多次查詢結果一致,不受其他事物幹擾
READ_UNCOMMITTED 會讀取到另一事務未提交的數據
READ_COMMITTED 不會讀取其他事務沒有提交的數據
REPEATABLE_READ 兩個事務同時讀取一條數據,B修改提交後,A讀取的數據不變 默認隔離級別
SERIALIZABLE 等同於oracle

REPEATABLE_READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據 無法測試
即使READ_COMMITTED下,在一個事務未提交時,另一個事務是無法提交修改的
undo:
用來保證數據讀一致的。數據再修改前會放入undo空間,如果修改失敗了回滾會用到undo中的信息。還有其他用戶正在被修改的數據時是都的undo表空間的數據,保證數據一致。redo是聯機日誌,沒有單獨的表空間

REPEATABLE_READ:
RR隔離級別下的一致性讀,不是以begin開始的時間點作為snapshot建立時間點,而是以第一條select語句的時間點作為snapshot建立的時間點
對同一個表或者不同表進行的第一次select語句建立了該事務中一致性讀的snapshot. 其它update, delete, insert 語句和一致性讀snapshot的建立沒有關系。在snapshot建立之後提交的數據,一致性讀就讀不到,之前提交的數據就可以讀到
事務的起始點其實是以執行的第一條語句為起始點的
http://www.cnblogs.com/digdeep/p/4947694.html

READ_COMMITTED:
事務中每一次讀取都是以當前的時間點作為判斷是否提交的實際

innodb的默認事務隔離級別是rr(可重復讀)。它的實現技術是mvcc。基於版本的控制協議。該技術不僅可以保證innodb的可重復讀,而且可以防止幻讀。但是它防止的是快照讀,也就是讀取的數據雖然是一致的,但是數據是歷史數據。如何做到保證數據是一致的(也就是一個事務,其內部讀取對應某一個數據的時候,數據都是一樣的),同時讀取的數據是最新的數據。innodb提供了一個間隙鎖的技術。也就是結合grap鎖與行鎖,達到最終目的。當使用索引進行插入的時候,innodb會將當前的節點和上一個節點加鎖。這樣當進行select的時候,就不允許加x鎖。那麽在進行該事務的時候,讀取的就是最新的數據。
實現:
1. 快照讀(snapshot read)
簡單的select操作(不包括 select ... lock in share mode, select ... for update)
2.當前讀(current read)
select ... lock in share mode
select ... for update
insert
update
delete
在RR級別下,快照讀是通過MVVC(多版本控制)和undo log來實現的,當前讀是通過加record lock(記錄鎖)和gap lock(間隙鎖)來實現的。
所以從上面的顯示來看,如果需要實時顯示數據,還是需要通過加鎖來實現。這個時候會使用next-key技術來實現。
總結:在mysql中,提供了兩種事務隔離技術,第一個是mvcc,第二個是next-key技術。這個在使用不同的語句的時候可以動態選擇。不加lock inshare mode之類的就使用mvcc。否則使用next-key。mvcc的優勢是不加鎖,並發性高。缺點是不是實時數據。next-key的優勢是獲取實時數據,但是需要加鎖。同時需要註意幾點:1.事務的快照時間點是以第一個select來確認的。所以即便事務先開始。但是select在後面的事務的update之類的語句後進行,那麽它是可以獲取後面的事務的對應的數據。2.mysql中數據的存放還是會通過版本記錄一系列的歷史數據,這樣,可以根據版本查找數據

mysqld -install
net start mysql
mysqld-nt --init-file=C:\mysql-init.txt
mysql -u root -p

sqlSession = sqlSessionFactory.openSession();
mapper = sqlSession.getMapper(UserMapper.class);
mapper.update(map);

net start oracleserviceORCL
Lsnrctl start
net stop oracleserviceORCL
Lsnrctl stop
管理員身份運行plsql 或 管理員身份打開cmd sqlplus命令 連接數據庫 再運行plsql

repo
C:\Program Files (x86)\Java\jdk1.6.0_05\
zooker啟動時需關閉:
service iptables stop

Spring對事務的解決辦法其實分為2種:編程式實現事務,AOP配置聲明式解決方案。
http://jinnianshilongnian.iteye.com/blog/1496953

Spring提供了許多內置事務管理器實現,常用的有以下幾種:

DataSourceTransactionManager:位於org.springframework.jdbc.datasource包中,數據源事務管理器,提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS框架的事務管理;
HibernateTransactionManager:位 於org.springframework.orm.hibernate3或者hibernate4包中,提供對單個 org.hibernate.SessionFactory事務支持,用於集成Hibernate框架時的事務管理;該事務管理器只支持 Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本;
JtaTransactionManager:位於org.springframework.transaction.jta包中,提供對分布式事務管理的支持,並將事務管理委托給Java EE應用服務器事務管理器;

Spring不僅提供這些事務管理器,還提供對如JMS事務管理的管理器
兩個不依賴於應用服務器的開源JTA事務實現組件:JOTM和Atomikos Transactions Essentials
具體用法參考http://jinnianshilongnian.iteye.com/blog/1439900

這篇博客講解了對於JDBC和JTA分別用PlatformTransactionManager實現和使用TransactionTemplate實現編程式事務管理:http://jinnianshilongnian.iteye.com/blog/1441271

編程式實現事務
Spring提供兩種編程式事務支持:直接使用PlatformTransactionManager實現和使用TransactionTemplate模板類,用於支持邏輯事務管理。
如果采用編程式事務推薦使用TransactionTemplate模板類。

Spring框架支持事務管理的核心是事務管理器抽象,對於不同的數據訪問框架(如Hibernate)通過實現策略接口 PlatformTransactionManager,從而能支持各種數據訪問框架的事務管理,PlatformTransactionManager 接口定義如下:

public interface PlatformTransactionManager {
//返回一個已經激活的事務或創建一個新的事務
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

關於TransactionDefinition接口和TransactionStatus接口:
http://jinnianshilongnian.iteye.com/blog/1439900

Spring聲明式事務
在日常開發中,用的最多的就是聲明式事務了,下面將介紹SpringJdbc的聲明式事務的配置方法:

<context:component-scan base-package="com.chou.spring.jdbc"/>

<!-- 配置數據源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClass" value="${db.driverClass}" />
<property name="jdbcUrl" value="${db.url}" />
<property name="user" value="${db.username}" />
<property name="password" value="${db.password}" />

<!-- Connection Pooling Info -->
<property name="initialPoolSize" value="1" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="15" />
<property name="maxIdleTime" value="1800" />
<property name="maxStatements" value="0" />
</bean>

<bean id="jdbcTemplateDao" class="com.chou.spring.jdbc.dao.JdbcTemplateDao" >
<property name="dataSource" ref="dataSource" />
</bean>

<!-- JDBC事務管理器 -->
<bean id="jdbctTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<tx:advice id="txAdvice" transaction-manager="jdbctTxManager">
<tx:attributes>
<tx:method name="domain*"/>
</tx:attributes>
</tx:advice>

<aop:config proxy-target-class="true">
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.chou.spring.jdbc.service.JdbcTemplateService.*(..))"/>
</aop:config>

public class JdbcTemplateDao extends JdbcDaoSupport{

public void save() {
String insertSql = "insert into tab_item values(?,?,?)";
Assert.isTrue(getJdbcTemplate().update(insertSql, new Object[]{6, "HP", "PT540"}) == 1, "插入失敗");
}

public void delete() {
String deleteSql = "delete tab_item where id = ?";
Assert.isTrue(getJdbcTemplate().update(deleteSql, new Object[]{6}) == 1, "刪除失敗");
}

public void update() {
String updateSql = "update tab_item set itemno = ?, itemname = ? where id = ?";
Assert.isTrue(getJdbcTemplate().update(updateSql, new Object[]{"HP", "PT555", 6}) == 1, "修改失敗");
}
}

/**
*
* @author Chou
* @since 2012-9-9
* 把事務定義在Service層是為了避免報錯:
* All calls to this method via a proxy will be routed directly to the proxy.
* 這是是事務轉移問題,你如果在控制層加入事務就不會有提示了,也沒有警告,
* 一般很多人在final DAO裏加入事務那是有警告的,
* 如果配置文件定義了AOP獲取代理對象是proxy-target-class="true"即采用CGLIB方式
* 而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,
* 通過修改其字節碼生成子類並繼承你寫的類,然後在你的基礎上加事物管理,
* 而JdbcDaoSupport中的setDataSource是final的他繼承不了
* 當然你可以無視它,也沒有問題。
*/
@Service
public class JdbcTemplateService {

@Autowired
private JdbcTemplateDao jdbcTemplateDao;

public void domain(){
jdbcTemplateDao.save();
int i = 2/0;//這裏出錯了,事務就會回滾,之前的save就無效了
jdbcTemplateDao.update();
jdbcTemplateDao.delete();
}
}

//main方法
String[] configLocations = new String[] {"applicationContext.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocations);
JdbcTemplateService j = ctx.getBean(JdbcTemplateService.class);
j.domain();

<tx:advice/>配置詳解

<tx:advice id="……" transaction-manager="……">
<tx:attributes>
<tx:method name="*"
propagation="REQUIRED"
isolation="DEFAULT"
timeout="-1"
read-only="true"
no-rollback-for=""
rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>

<!-- 最常用的配置 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" propagation="REQUIRED" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="count*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>


XML形式的事務配置<tx:method >的屬性詳解
屬性

類型

默認值

說明
propagation Propagation枚舉 REQUIRED 事務傳播屬性
isolation isolation枚舉 DEFAULT(所用數據庫默認級別) 事務隔離級別
readOnly boolean false 是否才用優化的只讀事務
timeout int -1 超時(秒)
rollbackFor Class[] {} 需要回滾的異常類
rollbackForClassName String[] {} 需要回滾的異常類名
noRollbackFor Class[] {} 不需要回滾的異常類
noRollbackForClassName String[] {} 不需要回滾的異常類名

readOnly
事務屬性中的readOnly標誌表示對應的事務應該被最優化為只讀事務。如果值為true就會告訴Spring我這個方法裏面沒有insert或者update,你只需要提供只讀的數據庫Connection就行了,這種執行效率會比read-write的Connection高,所以這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)時避免dirty checking(試圖“刷新”)。

timeout
在屬性中還有定義“timeout”值的選項,指定事務超時為幾秒。一般不會使用這個屬性。在JTA中,這將被簡單地傳遞到J2EE服務器的事務協調程序,並據此得到相應的解釋。

Isolation Level(事務隔離等級)的5個枚舉值
為什麽事務要有Isolation Level這個屬性?先回顧下數據庫事務的知識:
第一類丟失更新(lost update):在完全未隔離事務的情況下,兩個事物更新同一條數據資源,某一事物異常終止,回滾造成第一個完成的更新也同時丟失。
第二類丟失更新(second lost updates):是不可重復讀的特殊情況,如果兩個事務都讀取同一行,然後兩個都進行寫操作,並提交,第一個事務所做的改變就會丟失。
臟讀(dirty read):如果第二個事務查詢到第一個事務還未提交的更新數據,形成臟讀。因為第一個事務你還不知道是否提交,所以數據不一定是正確的。
虛讀(phantom read):一個事務執行兩次查詢,第二次結果集包含第一次中沒有或者某些行已被刪除,造成兩次結果不一致,只是另一個事務在這兩次查詢中間插入或者刪除了數據造成的。
不可重復讀(unrepeated read):一個事務兩次讀取同一行數據,結果得到不同狀態結果,如中間正好另一個事務更新了該數據,兩次結果相異,不可信任。

具體關於事務機制可以看我以前的博客:http://zhou137520.iteye.com/admin/blogs/1638574

當遇到以上這些情況時我們可以設置isolation下面這些枚舉值:
DEFAULT:采用數據庫默認隔離級別
SERIALIZABLE:最嚴格的級別,事務串行執行,資源消耗最大;
REPEATABLE_READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重復讀取”的情況,但是帶來了更多的性能損失。
READ_COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“臟讀取”。該級別適用於大多數系統。
READ_UNCOMMITTED:保證了讀取過程中不會讀取到非法數據。隔離級別在於處理多事務的並發問題。

關於propagation屬性的7個傳播行為
REQUIRED:指定當前方法必需在事務環境中運行,如果當前有事務環境就加入當前正在執行的事務環境,如果當前沒有事務,就新建一個事務。這是默認值。
SUPPORTS:指定當前方法加入當前事務環境,如果當前沒有事務,就以非事務方式執行。
MANDATORY:指定當前方法必須加入當前事務環境,如果當前沒有事務,就拋出異常。
REQUIRES_NEW:指定當前方法總是會為自己發起一個新的事務,如果發現當前方法已運行在一個事務中,則原有事務被掛起,我自己創建一個屬於自己的事務,直我自己這個方法commit結束,原先的事務才會恢復執行。
NOT_SUPPORTED:指定當前方法以非事務方式執行操作,如果當前存在事務,就把當前事務掛起,等我以非事務的狀態運行完,再繼續原來的事務。
NEVER:指定當前方法絕對不能在事務範圍內執行,如果方法在某個事務範圍內執行,容器就拋異常,只有沒關聯到事務,才正常執行。
NESTED:指定當前方法執行時, 如果已經有一個事務存在,則運行在這個嵌套的事務中.如果當前環境沒有運行的事務,就新建一個事務,並與父事務相互獨立,這個事務擁有多個可以回滾的保證 點。就是指我自己內部事務回滾不會對外部事務造成影響,只對DataSourceTransactionManager事務管理器起效。

註解形式@Transactional實現事務管理
註意@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能。

默認情況下,一個有事務的方法,遇到RuntiomeException時會回滾。遇到受檢查的異常是不會回滾的,要想所有異常都回滾,要加上屬性rollbackFor={Exception.class}

<!-- 事務管理器配置 -->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- 使用annotation定義事務 -->
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />


transaction-manager:指定事務管理器名字,默認為transactionManager,當使用其他名字時需要明確指定;
proxy-target-class:默認false表示使用JDK代理,如果為true將使用CGLIB代理
order:定義事務通知順序,默認Ordered.LOWEST_PRECEDENCE,表示將順序決定權交給AOP來處理。

建議只在實現類或實現類的方法上使用@Transactional,而不要在接口上使用,這是因為如果使用JDK代理機制是沒問題,因為其使用基於接口的代理;而使用使用CGLIB代理機制時就會遇到問題,因為其使用基於類的代理而不是接口,這是因為接口上的@Transactional註解是“不能繼承的”。
http://jinnianshilongnian.iteye.com/blog/1508018這篇博客講解了基於JDK動態代理和CGLIB動態代理的實現Spring註解管理事務(@Trasactional)到底有什麽區別。

@Transactional//放在這裏表示所有方法都加入事務管理
public class AnnotationUserServiceImpl implements IUserService {
private IUserDao userDao;
private IAddressService addressService;

@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
public void save(UserModel user) {
userDao.save(user);
user.getAddress().setUserId(user.getId());
addressService.save(user.getAddress());
}

@Transactional(propagation=Propagation.REQUIRED, readOnly=true,
isolation=Isolation.READ_COMMITTED)
public int countAll() {
return userDao.countAll();
}
//setter...
}


總結


編程式事務是不推薦的,即使有很少事務操作,Spring發展到現在,沒有理由使用編程式事務,只有在為了深入理解Spring事務管理才需要學習編程式事務使用。
推薦使用聲明式事務,而且強烈推薦使用<tx:tags>方式的聲明式事務,因為其是無侵入代碼的,可以配置模板化的事務屬性並運用到多個項目中。
而@Transaction註解事務,不過如果一個項目模塊太多,service方法太多導致每個方法都要手動去加註解,是不是很麻煩,也容易出錯。如果一個項目結構清晰,分層明確,那麽標簽形式的配置將是最直觀和方便的辦法。
總之,能保證項目正常工作的事務配置就是最好的。

數據庫事物