環(huán)境:Spring5.3.23
Spring事務(wù)超時(shí)是指一個(gè)事務(wù)在執(zhí)行中最長(zhǎng)的允許時(shí)間。如果事務(wù)在超時(shí)時(shí)間內(nèi)未能完成,則會(huì)自動(dòng)回滾。超時(shí)時(shí)間可以通過(guò)設(shè)置來(lái)控制,以確保事務(wù)在規(guī)定的時(shí)間內(nèi)完成或回滾,避免數(shù)據(jù)一致性問(wèn)題。
在工作中你有配置事務(wù)的超時(shí)時(shí)間嗎?如何進(jìn)行配置事務(wù)超時(shí)時(shí)間?
注解方式:
// 這里單位是s@Transactional(timeout = 2)public void save() {}
編程方式1:
@Resourceprivate PlatformTransactionManager tm ;DefaultTransactionDefinition definition = new DefaultTransactionDefinition();definition.setTimeout(2) ;
編程方式2:
@Resourceprivate PlatformTransactionManager tm ;public void update() { TransactionTemplate template = new TransactionTemplate(tm) ; template.setTimeout(2) ; template.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { // ... return null ; } }) ;}
以上3種方式讀可以進(jìn)行事務(wù)超時(shí)的設(shè)置,什么情況下才能算是事務(wù)超時(shí)呢?
準(zhǔn)備一張2000w數(shù)據(jù)的表
圖片
表字段信息如下:
圖片
此表除主鍵外沒(méi)有任何的索引。
圖片
統(tǒng)計(jì)查詢表的數(shù)據(jù)量,將該操作放到一個(gè)事務(wù)中,同時(shí)設(shè)置事務(wù)的超時(shí)時(shí)間。
// 將超時(shí)時(shí)間設(shè)置為20s@Transactional(timeout = 20)public void query() { long start = System.currentTimeMillis() ; jdbcTemplate.execute("select count(*) from p_user") ; System.out.println("耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;}
執(zhí)行結(jié)果:
耗時(shí):3198毫秒
接下來(lái)將超時(shí)時(shí)間改成3s
執(zhí)行結(jié)果如下:
13:56:01.425 [main] WARN c.zaxxer.hikari.pool.ProxyConnection - HikariPool-1 - Connection com.mysql.cj.jdbc.ConnectionImpl@504ecd marked as broken because of SQLSTATE(null), ErrorCode(0)com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:113) at com.mysql.cj.jdbc.StatementImpl.checkCancelTimeout(StatementImpl.java:2167)
從異常信息看到拋出了超時(shí)異常。這里的超時(shí)是sql執(zhí)行的超時(shí),是由我們的驅(qū)動(dòng)程序拋出的異常。
模擬其它非數(shù)據(jù)庫(kù)操作耗時(shí),而這個(gè)是在執(zhí)行數(shù)據(jù)庫(kù)操作之前
@Transactional(timeout = 2)public void query() { long start = System.currentTimeMillis() ; try { TimeUnit.SECONDS.sleep(3) ; } catch (InterruptedException e) { e.printStackTrace(); } // 執(zhí)行非常簡(jiǎn)單的操作 jdbcTemplate.execute("select 1") ; System.out.println("耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;}
執(zhí)行結(jié)果:
14:08:44.000 [main] DEBUG o.s.jdbc.core.JdbcTemplate - Executing SQL statement [select 1]Exception in thread "main" org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Oct 11 14:08:42 CST 2023 at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:155)
拋出了超時(shí)異常,而這個(gè)異常是由Spring框架拋出的。
模擬其它非數(shù)據(jù)庫(kù)操作耗時(shí),而這個(gè)是在執(zhí)行數(shù)據(jù)庫(kù)操作之后,適當(dāng)調(diào)整上面的代碼順序
@Transactional(timeout = 2)public void query() { long start = System.currentTimeMillis() ; jdbcTemplate.execute("select 1") ; try { TimeUnit.SECONDS.sleep(3) ; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ;}
執(zhí)行結(jié)果:
耗時(shí):3015毫秒
程序正常運(yùn)行
在測(cè)試3的基礎(chǔ)上,最后再次執(zhí)行數(shù)據(jù)庫(kù)相關(guān)的操作
@Transactional(timeout = 2)public void query() { long start = System.currentTimeMillis() ; jdbcTemplate.execute("select 1") ; try { TimeUnit.SECONDS.sleep(3) ; } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("耗時(shí):" + (System.currentTimeMillis() - start) + "毫秒") ; // 再次執(zhí)行數(shù)據(jù)庫(kù)相關(guān)操作 jdbcTemplate.execute("select 1") ;}
執(zhí)行結(jié)果:
耗時(shí):3024毫秒14:14:38.257 [main] DEBUG o.s.jdbc.core.JdbcTemplate - Executing SQL statement [select 1]Exception in thread "main" org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Oct 11 14:14:37 CST 2023 at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:155)
第一個(gè)數(shù)據(jù)庫(kù)操作,沒(méi)有拋出異常,直到第二個(gè)執(zhí)行時(shí)拋出了異常。
總結(jié): 事務(wù)方法開(kāi)始執(zhí)行時(shí)就開(kāi)始計(jì)時(shí),在執(zhí)行到數(shù)據(jù)庫(kù)操作時(shí)判斷當(dāng)前的執(zhí)行時(shí)間點(diǎn)是否已經(jīng)超過(guò)了設(shè)置的超時(shí)時(shí)間,如果是則拋出Timeout異常。
在開(kāi)始一個(gè)事務(wù)時(shí)會(huì)在DataSourceTransactionManager#doBegin方法中設(shè)置超時(shí)時(shí)間
protected void doBegin(Object transaction, TransactionDefinition definition) { int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { // 如果注解@Transactional中設(shè)置了timeout,則設(shè)置超時(shí)時(shí)間 txObject.getConnectionHolder().setTimeoutInSeconds(timeout); }}protected int determineTimeout(TransactionDefinition definition) { if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) { return definition.getTimeout(); } return getDefaultTimeout();}
當(dāng)通過(guò)JdbcTemplate操作數(shù)據(jù)庫(kù)時(shí),還會(huì)執(zhí)行如下操作
public class JdbcTemplate { private <T> T execute(StatementCallback<T> action, boolean closeResources) { Statement stmt = null; try { stmt = con.createStatement(); // 配置Statement對(duì)象,這其中會(huì)設(shè)置超時(shí)時(shí)間 applyStatementSettings(stmt); // ... return result; } } protected void applyStatementSettings(Statement stmt) throws SQLException { // ... // getQueryTimeout方法返回的是當(dāng)前JdbcTemplate對(duì)象中設(shè)置d餓超時(shí)時(shí)間 DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); }}
DataSourceUtils工具類
public abstract class DataSourceUtils { public static void applyTimeout(Statement stmt, @Nullable DataSource dataSource, int timeout) throws SQLException { ConnectionHolder holder = null; if (dataSource != null) { holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); } // 如果當(dāng)前事務(wù)執(zhí)行配置了超時(shí)時(shí)間 if (holder != null && holder.hasTimeout()) { // 剩余事務(wù)超時(shí)將覆蓋指定的值。 stmt.setQueryTimeout(holder.getTimeToLiveInSeconds()); } else if (timeout >= 0) { // No current transaction timeout -> apply specified value. stmt.setQueryTimeout(timeout); } } }
ResourceHolderSupport類
public abstract class ResourceHolderSupport implements ResourceHolder { public int getTimeToLiveInSeconds() { double diff = ((double) getTimeToLiveInMillis()) / 1000; int secs = (int) Math.ceil(diff); // 檢查超時(shí)時(shí)間 checkTransactionTimeout(secs <= 0); return secs; } private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException { if (deadlineReached) { // 設(shè)置事務(wù)回滾 setRollbackOnly(); // 拋出異常 throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline); } }}
完畢!!!
本文鏈接:http://www.www897cc.com/showinfo-26-13520-0.htmlSpring事務(wù)超時(shí)到底是怎么回事?
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com