序
本文主要介紹下jdbc的socket timeout的設(shè)置
jdbc timeout類別
主要有如下幾個(gè)類別
- transaction timeout
設(shè)置的是一個(gè)事務(wù)的執(zhí)行時(shí)間涮母,里頭可能包含多個(gè)statement
- statement timeout(
也相當(dāng)于result set fetch timeout
)
設(shè)置的是一個(gè)statement的執(zhí)行超時(shí)時(shí)間沛贪,即driver等待statement執(zhí)行完成,接收到數(shù)據(jù)的超時(shí)時(shí)間(
注意statement的timeout不是整個(gè)查詢的timeout,只是statement執(zhí)行完成并拉取fetchSize數(shù)據(jù)返回的超時(shí)豺总,之后resultSet的next在必要的時(shí)候還會(huì)觸發(fā)fetch數(shù)據(jù),每次fetch的超時(shí)時(shí)間是單獨(dú)算的信认,默認(rèn)也是以statement設(shè)置的timeout為準(zhǔn)
)
- jdbc socket timeout
設(shè)置的是jdbc I/O socket read and write operations的超時(shí)時(shí)間惧磺,防止因網(wǎng)絡(luò)問題或數(shù)據(jù)庫問題,導(dǎo)致driver一直阻塞等待赏寇。(
建議比statement timeout的時(shí)間長
)
- os socket timeout
這個(gè)是操作系統(tǒng)級(jí)別的socket設(shè)置(
如果jdbc socket timeout沒有設(shè)置吉嫩,而os級(jí)別的socket timeout有設(shè)置,則使用系統(tǒng)的socket timeout值
)嗅定。
上面的不同級(jí)別的timeout越往下優(yōu)先級(jí)越高自娩,也就是說如果下面的配置比上面的配置值小的話,則會(huì)優(yōu)先觸發(fā)timeout渠退,那么相當(dāng)于上面的配置值就"失效"了忙迁。
jdbc socket timeout
這個(gè)不同數(shù)據(jù)的jdbc driver實(shí)現(xiàn)不一樣
mysql
jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000
通過url參數(shù)傳遞即可
pg
jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60
pg也是通過url傳遞,不過它的單位與mysql不同碎乃,mysql是毫秒姊扔,而pg是秒
oracle
oracle需要通過oracle.jdbc.ReadTimeout參數(shù)來設(shè)置,連接超時(shí)參數(shù)是oracle.net.CONNECT_TIMEOUT
- 通過properties設(shè)置
Class.forName("oracle.jdbc.driver.OracleDriver");
Properties props = new Properties() ;
props.put( "user" , "test_schema") ;
props.put( "password" , "pwd") ;
props.put( "oracle.net.CONNECT_TIMEOUT" , "10000000") ;
props.put( "oracle.jdbc.ReadTimeout" , "2000" ) ;
Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@10.0.1.9:1521:orcl" , props ) ;
- 通過環(huán)境變量設(shè)置
String readTimeout = "10000"; // ms
System.setProperty("oracle.jdbc.ReadTimeout", readTimeout);
Class.forName("oracle.jdbc.OracleDriver");
Connection conn = DriverManager.getConnection(jdbcUrl, user, pwd);
注意需要在connection連接之前設(shè)置環(huán)境變量
- tomcat jdbc pool
一般我們不直接使用jdbc connection荠锭,而是使用連接池旱眯。由于tomcat jdbc pool是springboot默認(rèn)使用的數(shù)據(jù)庫連接池,這里就講述一下如何在tomcat jdbc pool下設(shè)置证九。
spring.datasource.tomcat.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000
注意删豺,這里是分號(hào)分隔,單位是毫秒愧怜,這里可以根據(jù)各自的情況配置前綴(
tomcat jdbc連接池的話呀页,默認(rèn)是spring.datasource.tomcat
),可以自定義拥坛,比如
@Bean
@Qualifier("writeDataSource")
@ConfigurationProperties(prefix = "spring.datasource.write")
public DataSource writeDataSource() {
return DataSourceBuilder.create().build();
}
假設(shè)你這里是自定義了prefix為spring.datasource.write蓬蝶,那么上述配置就變?yōu)?/p>
spring.datasource.write.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000
oracle.jdbc.ReadTimeout如果沒有設(shè)置的話尘分,driver里頭默認(rèn)是0
oracle.jdbc.ReadTimeout
driver內(nèi)部將該值設(shè)置到oracle.net.READ_TIMEOUT變量上
- oracle.net.nt.TcpNTAdapter
@Override
public void setReadTimeoutIfRequired(final Properties properties) throws IOException, NetException {
String s = ((Hashtable<K, String>)properties).get("oracle.net.READ_TIMEOUT");
if (s == null) {
s = "0";
}
this.setOption(3, s);
}
public void setOption(int var1, Object var2) throws IOException, NetException {
String var3;
switch(var1) {
case 0:
var3 = (String)var2;
this.socket.setTcpNoDelay(var3.equals("YES"));
break;
case 1:
var3 = (String)var2;
if(var3.equals("YES")) {
this.socket.setKeepAlive(true);
}
case 2:
default:
break;
case 3:
this.sockTimeout = Integer.parseInt((String)var2);
this.socket.setSoTimeout(this.sockTimeout);
}
}
可用看到最后設(shè)置的是socket的soTimeout
實(shí)例
@Test
public void testReadTimeout() throws SQLException {
Connection connection = dataSource.getConnection();
String sql = "select * from demo_table";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement)connection.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
int col = rs.getMetaData().getColumnCount();
System.out.println("============================");
while (rs.next()) {
for (int i = 1; i <= col; i++) {
System.out.print(rs.getObject(i));
}
System.out.println("");
}
System.out.println("============================");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//close resources
}
}
超時(shí)錯(cuò)誤輸出
//部分?jǐn)?shù)據(jù)輸出......
java.sql.SQLRecoverableException: IO 錯(cuò)誤: Socket read timed out
at oracle.jdbc.driver.T4CPreparedStatement.fetch(T4CPreparedStatement.java:1128)
at oracle.jdbc.driver.OracleResultSetImpl.close_or_fetch_from_next(OracleResultSetImpl.java:373)
at oracle.jdbc.driver.OracleResultSetImpl.next(OracleResultSetImpl.java:277)
at com.example.demo.DemoApplicationTests.testReadTimeout(DemoApplicationTests.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: oracle.net.ns.NetException: Socket read timed out
at oracle.net.ns.Packet.receive(Packet.java:339)
at oracle.net.ns.DataPacket.receive(DataPacket.java:106)
at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:315)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:260)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:185)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:102)
at oracle.jdbc.driver.T4CSocketInputStreamWrapper.readNextPacket(T4CSocketInputStreamWrapper.java:124)
at oracle.jdbc.driver.T4CSocketInputStreamWrapper.read(T4CSocketInputStreamWrapper.java:80)
at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1137)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:290)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:531)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:207)
at oracle.jdbc.driver.T4CPreparedStatement.fetch(T4CPreparedStatement.java:1119)
... 35 more
剛開始會(huì)有數(shù)據(jù)輸出,但是到了某個(gè)resultSet的next的時(shí)候丸氛,報(bào)了超時(shí)(
close_or_fetch_from_next
)培愁,這個(gè)超時(shí)指定的是當(dāng)result.next方法觸發(fā)新的一批數(shù)據(jù)的拉取(當(dāng)一個(gè)fetchSize的數(shù)據(jù)消費(fèi)完之后,接下來的next會(huì)觸發(fā)新一批數(shù)據(jù)的fetch
)之后在timeout時(shí)間返回內(nèi)沒有收到數(shù)據(jù)庫返回的數(shù)據(jù)缓窜。
oracle的jdbc默認(rèn)的fetchSize為10定续,也就是每個(gè)fetch,如果超過指定時(shí)間沒接收到數(shù)據(jù)禾锤,則拋出timeout異常私股。
小結(jié)
jdbc的socketTimeout值的設(shè)置要非常小心,不同數(shù)據(jù)庫的jdbc driver設(shè)置不一樣恩掷,特別是使用不同連接池的話倡鲸,設(shè)置也可能不盡相同。對于嚴(yán)重依賴數(shù)據(jù)庫操作的服務(wù)來說黄娘,非常有必要設(shè)置這個(gè)值峭状,否則萬一網(wǎng)絡(luò)或數(shù)據(jù)庫異常,會(huì)導(dǎo)致服務(wù)線程一直阻塞在java.net.SocketInputStream.socketRead0逼争。
- 如果查詢數(shù)據(jù)多宁炫,則會(huì)導(dǎo)致該線程持有的data list不能釋放,相當(dāng)于內(nèi)存泄露氮凝,最后導(dǎo)致OOM
- 如果請求數(shù)據(jù)庫操作很多且阻塞住了,會(huì)導(dǎo)致服務(wù)器可用的woker線程變少望忆,嚴(yán)重則會(huì)導(dǎo)致服務(wù)不可用罩阵,nginx報(bào)504 Gateway Timeout