數(shù)據(jù)庫事務(wù)的基本知識(shí)
ACID
兩類丟失更新
事務(wù)回滾丟失更新:
目前大部分?jǐn)?shù)據(jù)庫已經(jīng)通過鎖的機(jī)制來避免了事務(wù)回滾丟失更新。
數(shù)據(jù)庫鎖的機(jī)制:
鎖可以分為樂觀鎖和悲觀鎖,而悲觀鎖又分為:讀鎖(共享鎖)和寫鎖(排它鎖),而數(shù)據(jù)庫實(shí)現(xiàn)了悲觀鎖中的讀鎖和寫鎖,而樂觀鎖則需要開發(fā)人員自己實(shí)現(xiàn)。
數(shù)據(jù)庫在設(shè)計(jì)這兩種鎖的時(shí)候,這兩種鎖間的關(guān)系如下:讀鎖與讀鎖可以共存,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。
比如說,當(dāng)a操作某條數(shù)據(jù)時(shí),數(shù)據(jù)庫就會(huì)給這條數(shù)據(jù)加鎖,其他人只能查看這條數(shù)據(jù),但是卻不能操作,只有當(dāng)a事務(wù)提交結(jié)束以后,鎖被取消了,其他人才可以修改這條數(shù)據(jù)。
事務(wù)提交丟失更新
這是高并發(fā)編程中需要重點(diǎn)關(guān)注的問題,數(shù)據(jù)庫為了壓制此類丟失更新,提出了事務(wù)之間的隔離級(jí)別的概念。
數(shù)據(jù)庫事務(wù)的隔離級(jí)別
未提交讀
允許一個(gè)事務(wù)讀取到另一個(gè)事務(wù)沒有提交的數(shù)據(jù)。
這種隔離級(jí)別可以擁有很好的并發(fā)能力,但是對(duì)于數(shù)據(jù)的一致性無法保證,所以適用于追求高并發(fā)性,但是對(duì)數(shù)據(jù)一致性要求低的場(chǎng)景。
另外,未提交讀的隔離級(jí)別會(huì)造成臟讀的現(xiàn)象:
讀寫提交
一個(gè)事務(wù)只能讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù),而不能讀取未提交的數(shù)據(jù)。
這種隔離級(jí)別可以避免臟讀的發(fā)生。
雖然讀寫提交的隔離級(jí)別克服了臟讀的發(fā)生,但是又會(huì)出現(xiàn)不可重復(fù)讀的現(xiàn)象:
可重復(fù)讀
可重復(fù)讀的隔離級(jí)別是為了克服不可重復(fù)讀的問題:
可重復(fù)讀的隔離級(jí)別雖然克服了不可重復(fù)讀的問題,但是會(huì)引入幻讀的問題:
幻讀和可重復(fù)讀的區(qū)別:
幻讀是針對(duì)于統(tǒng)計(jì)的場(chǎng)景,而可重復(fù)讀是針對(duì)于一條數(shù)據(jù)而言的。
串行化
為了解決上述的各種問題,數(shù)據(jù)庫提出了串行化的隔離級(jí)別。
這種級(jí)別下,所有的sql都會(huì)按照順序執(zhí)行,可以完全保證數(shù)據(jù)的一致性。
合理使用數(shù)據(jù)庫隔離級(jí)別
雖然串行化可以解決臟讀、不可重復(fù)讀、幻讀等問題,但是它也有一個(gè)很顯著的特點(diǎn),就是并發(fā)能力低下,這四種隔離級(jí)別的并發(fā)能力排名如下:
未提交讀 > 讀寫提交 > 可重復(fù)讀 > 串行化
所以使用時(shí)需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來權(quán)衡使用。
另外,不同數(shù)據(jù)對(duì)于隔離級(jí)別的支持也是不一樣的,比如:
Oracle:讀寫提交、串行化
MySQL:未提交讀、讀寫提交、可重復(fù)讀、串行化
PG:讀寫提交、可重復(fù)讀、串行化
Spring數(shù)據(jù)庫事務(wù)簡(jiǎn)介:
在 Spring 中,事務(wù)管理器的頂層接口為PlatformTransactionManager,Spring 還為此定義了一系列的接口和類,它們之間的關(guān)系如圖所示:
當(dāng)我們引入其它框架時(shí),還會(huì)有其它的事務(wù)管理器的類,比方說我們引入 Hibernate ,那么 Spring還會(huì)提供HibernateTransactionManager 與之對(duì)應(yīng)并給我們使用 。這里我們以 MyBatis 框架為例,去討論Spring 數(shù)據(jù)庫事務(wù)方面的問題,最常用到的事務(wù)管理器是 DataSourceTransactionManager 。從上圖中可以看到它也是一個(gè)實(shí)現(xiàn)了接口 PlatfonnTransactionManager 的類。
PlatfonnTransactionManager接口的源碼:
下面我們看一下PlatfonnTransactionManager接口的源碼:
public interface PlatformTransactionManager {
// 獲取事務(wù)
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 事務(wù)提交
void commit(TransactionStatus status) throws TransactionException;
// 事務(wù)回滾
void rollback(TransactionStatus status) throws TransactionException;
}
可以看到它里面定義了獲取事務(wù)、提交事務(wù)、回滾事務(wù)的方法。而mybatis的事務(wù)管理器DataSourceTransactionManager實(shí)現(xiàn)了PlatformTransactionManager 接口,所以它也擁有了這些方法。
事務(wù)的傳播行為
所謂傳播行為,就是方法之間調(diào)用時(shí),事務(wù)采取的策略。
在Spring的事務(wù)機(jī)制中,對(duì)于數(shù)據(jù)庫而言存在7中傳播行為,它們都被定義在Propagation枚舉中,該枚舉源碼如下:
public enum Propagation {
// 需要事務(wù),它也是默認(rèn)的傳播行為
// 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中一起運(yùn)行
// 如果當(dāng)前不存在事務(wù),就新建一個(gè)事務(wù)來運(yùn)行方法
// 使用頻次高
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
// 支持事務(wù)
// 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中一起運(yùn)行
// 如果不存在,就繼續(xù)采用無事務(wù)的方式運(yùn)行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
// 必須使用事務(wù)
// 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中一起運(yùn)行
// 如果不存在事務(wù),就拋出異常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
// 無論當(dāng)前是否存在事務(wù),都會(huì)創(chuàng)建一個(gè)新的事務(wù)來運(yùn)行
// 使用頻次高
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
// 不支持事務(wù)
// 如果當(dāng)前存在事務(wù),將掛起事務(wù)運(yùn)行
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
// 不支持事務(wù)
// 如果存在事務(wù),就拋出異常
// 如果不存在,就繼續(xù)采用無事務(wù)的方式運(yùn)行
NEVER(TransactionDefinition.PROPAGATION_NEVER),
// 事務(wù)嵌套
// 當(dāng)前方法調(diào)用子方法時(shí),如果子方法出現(xiàn)異常,則只回滾子方法執(zhí)行過的sql,不會(huì)回滾當(dāng)前方法的事務(wù)
// 使用頻次高
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
REQUIRED、REQUIRES_NEW、NESTED這三種使用頻次較高,需要重點(diǎn)關(guān)注。
@Transactional注解
Spring對(duì)于事務(wù)的處理主要采用聲明式事務(wù)的方式,也就是通過@Transactional注解來進(jìn)行數(shù)據(jù)庫事務(wù)的管理。
下面是它的源碼:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 通過bean的name來指定事務(wù)管理器
@AliasFor("transactionManager")
String value() default "";
// 和value屬性一樣
@AliasFor("value")
String transactionManager() default "";
// 設(shè)置事務(wù)傳播行為
Propagation propagation() default Propagation.REQUIRED;
// 設(shè)置事務(wù)隔離級(jí)別
Isolation isolation() default Isolation.DEFAULT;
// 指定超時(shí)時(shí)間(單位:秒)
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只讀事務(wù)
boolean readOnly() default false;
// 方法在發(fā)生指定異常時(shí)進(jìn)行回滾,默認(rèn)是所有異常都會(huì)回滾
Class<? extends Throwable>[] rollbackFor() default {};
// 方法在發(fā)生指定異常名稱時(shí)進(jìn)行回滾,默認(rèn)是所有異常都會(huì)回滾
String[] rollbackForClassName() default {};
// 方法在發(fā)生指定異常時(shí)不進(jìn)行回滾,默認(rèn)是所有異常都會(huì)回滾
Class<? extends Throwable>[] noRollbackFor() default {};
// 方法在發(fā)生指定異常名稱時(shí)不進(jìn)行回滾,默認(rèn)是所有異常都會(huì)回滾
String[] noRollbackForClassName() default {};
}
可以看到,我們?cè)谑褂聾Transactional注解時(shí),是可以自己設(shè)置事務(wù)的隔離級(jí)別、傳播行為、回滾機(jī)制等等。
@Transactional實(shí)戰(zhàn)
下面讓我們一起實(shí)際應(yīng)用下@Transactional對(duì)于數(shù)據(jù)庫事務(wù)的控制。
首先我們創(chuàng)建一個(gè)保存數(shù)據(jù)庫的,代碼如下:
REQUIRED傳播行為測(cè)試
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
......
@Autowired
@Lazy
private IUserService userService;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public int insert(User user) {
long oldId = user.getId();
user.setId(null);
userMapper.insert(user);
if(oldId == 10){
throw new IllegalArgumentException("事務(wù)回滾測(cè)試" + user.getId());
}
user.setUpdatedBy(10288931L);
return userMapper.update(user);
}
@Override
public boolean save(int times) {
for(int timesTmp = 1; timesTmp <= times; timesTmp++){
User user = easyRandom.nextObject(User.class);
user.setId((long) timesTmp);
try {
userService.insert(user);
}catch (Exception e){
log.warn("出現(xiàn)了異常:{}", e.getMessage());
}
}
return true;
}
}
可以看到,我們將方法insert的事務(wù)傳播行為設(shè)置成了REQUIRED,也就是:
// 需要事務(wù),它也是默認(rèn)的傳播行為
// 如果當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中一起運(yùn)行
// 如果當(dāng)前不存在事務(wù),就新建一個(gè)事務(wù)來運(yùn)行方法
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
注意:這里調(diào)用insert方法時(shí)需要通過代理對(duì)象調(diào)用,因?yàn)镾pring對(duì)于事務(wù)的處理是基于AOP實(shí)現(xiàn)的,所以如果不通過代理對(duì)象調(diào)用,就無法觸發(fā)事務(wù),也就是事務(wù)會(huì)失效。
然后將日志級(jí)別設(shè)置為DEBUG,這樣就可以看到事務(wù)相關(guān)的日志了:
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
按照REQUIRED傳播行為的特點(diǎn),此時(shí)調(diào)用它的save方法是沒有事務(wù)的,所以insert方法會(huì)單獨(dú)創(chuàng)建自己的事務(wù)來運(yùn)行,然后我們創(chuàng)建測(cè)試用的mapper和controller(這兩個(gè)就省略了,寫法都很簡(jiǎn)單),調(diào)用接口,可以看到日志如下:
可以看到我新增了5個(gè)user信息,日志中也是給insert方法創(chuàng)建了5個(gè)事務(wù)來運(yùn)行,這和REQUIRED傳播行為的特點(diǎn)是一致的。
此時(shí)我們給save方法也加上日志,如下所示:
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
這個(gè)時(shí)候,對(duì)于insert方法來說,就是有事務(wù)的環(huán)境了,按照REQUIRED傳播行為的特點(diǎn),insert方法就不會(huì)自己?jiǎn)为?dú)創(chuàng)建事務(wù)了,而是沿用save方法的事務(wù),我們?cè)俅握{(diào)用接口,得到日志如下:
可以看到只是給save方法創(chuàng)建了事務(wù),并沒有給insert方法單獨(dú)創(chuàng)建事務(wù)
可以看到insert方法運(yùn)行時(shí),是加入到原有的事務(wù)之中的,這和REQUIRED傳播行為的特點(diǎn)是一致的。
REQUIRES_NEW傳播行為測(cè)試
現(xiàn)在我們將insert方法的事務(wù)傳播行為設(shè)置為REQUIRES_NEW,其特點(diǎn)如下所示:
// 無論當(dāng)前是否存在事務(wù),都會(huì)創(chuàng)建一個(gè)新的事務(wù)來運(yùn)行
接下來我們?cè)僬{(diào)用一下接口,得到如下日志:
可以看到這里是創(chuàng)建了6個(gè)事務(wù),其中有一個(gè)是save方法的事務(wù),其他5個(gè)都是insert方法自己的事務(wù)。這和REQUIRED傳播行為的特點(diǎn)是一致的。
NESTED傳播行為測(cè)試
// 事務(wù)嵌套
// 如果沒有事務(wù),則創(chuàng)建一個(gè)自己的事務(wù)
// 如果當(dāng)前有事務(wù),則創(chuàng)建一個(gè)嵌套的事務(wù)
// 當(dāng)前方法調(diào)用子方法時(shí),如果子方法出現(xiàn)異常,則只回滾子方法執(zhí)行過的sql,不會(huì)回滾當(dāng)前方法的事務(wù)
更改insert方法的事務(wù)如下:
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NESTED, rollbackFor = Exception.class)
同時(shí)去掉save方法的事務(wù),再次調(diào)用接口,得到如下日志:
此時(shí)save方法沒有添加事務(wù),所以按照NESTED傳播行為的特點(diǎn),insert方法會(huì)創(chuàng)建自己的事務(wù)。
然后我們?cè)賹ave方法添加上事務(wù),再次調(diào)用接口,得到日志如下:
因?yàn)榇藭r(shí)save方法添加了事務(wù),所以insert方法會(huì)創(chuàng)建一個(gè)嵌套在save方法里面的事務(wù)。
有個(gè)注意點(diǎn):對(duì)于嵌套事務(wù)來說,當(dāng)前方法調(diào)用子方法時(shí),如果子方法出現(xiàn)異常,則只回滾子方法執(zhí)行過的sql,不會(huì)回滾當(dāng)前方法的事務(wù),這個(gè)效果是通過數(shù)據(jù)庫的保存點(diǎn)技術(shù)來實(shí)現(xiàn)的,至于數(shù)據(jù)庫的保存點(diǎn)技術(shù),可以自行了解一下。文章來源:http://www.zghlxwxcb.cn/news/detail-469224.html
好了,今天就到這里了,感興趣的小伙伴趕緊去試試吧,拜拜。文章來源地址http://www.zghlxwxcb.cn/news/detail-469224.html
到了這里,關(guān)于Spring數(shù)據(jù)庫事務(wù)處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!