聊聊jdbc socketTimeout的設置

本文主要介紹下jdbc的socket timeout的設置html

jdbc timeout類別

主要有以下幾個類別
java

  • transaction timeout
設置的是一個事務的執行時間,裏頭可能包含多個statement
  • statement timeout(也至關於result set fetch timeout)
設置的是一個statement的執行超時時間,即driver等待statement執行完成,接收到數據的超時時間( 注意statement的timeout不是整個查詢的timeout,只是statement執行完成並拉取fetchSize數據返回的超時,以後resultSet的next在必要的時候還會觸發fetch數據,每次fetch的超時時間是單獨算的,默認也是以statement設置的timeout爲準)
  • jdbc socket timeout
設置的是jdbc I/O socket read and write operations的超時時間,防止因網絡問題或數據庫問題,致使driver一直阻塞等待。( 建議比statement timeout的時間長)
  • os socket timeout
這個是操做系統級別的socket設置( 若是jdbc socket timeout沒有設置,而os級別的socket timeout有設置,則使用系統的socket timeout值)。

上面的不一樣級別的timeout越往下優先級越高,也就是說若是下面的配置比上面的配置值小的話,則會優先觸發timeout,那麼至關於上面的配置值就"失效"了。mysql

jdbc socket timeout

這個不一樣數據的jdbc driver實現不同

mysql

jdbc:mysql://localhost:3306/ag_admin?useUnicode=true&characterEncoding=UTF8&connectTimeout=60000&socketTimeout=60000
經過url參數傳遞便可

pg

jdbc:postgresql://localhost/test?user=fred&password=secret&&connectTimeout=60&socketTimeout=60
pg也是經過url傳遞,不過它的單位與mysql不一樣,mysql是毫秒,而pg是秒

oracle

oracle須要經過oracle.jdbc.ReadTimeout參數來設置,鏈接超時參數是oracle.net.CONNECT_TIMEOUT
  • 經過properties設置
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 ) ;
  • 經過環境變量設置
String readTimeout = "10000"; // ms
System.setProperty("oracle.jdbc.ReadTimeout", readTimeout);
Class.forName("oracle.jdbc.OracleDriver");
Connection conn = DriverManager.getConnection(jdbcUrl, user, pwd);
注意須要在connection鏈接以前設置環境變量
  • tomcat jdbc pool
通常咱們不直接使用jdbc connection,而是使用鏈接池。因爲tomcat jdbc pool是springboot默認使用的數據庫鏈接池,這裏就講述一下如何在tomcat jdbc pool下設置。
spring.datasource.tomcat.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000
注意,這裏是分號分隔,單位是毫秒,這裏能夠根據各自的狀況配置前綴( tomcat jdbc鏈接池的話,默認是spring.datasource.tomcat),能夠自定義,好比
@Bean
    @Qualifier("writeDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.write")
    public DataSource writeDataSource() {
        return DataSourceBuilder.create().build();
    }
假設你這裏是自定義了prefix爲spring.datasource.write,那麼上述配置就變爲
spring.datasource.write.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000
oracle.jdbc.ReadTimeout若是沒有設置的話,driver裏頭默認是0

oracle.jdbc.ReadTimeout

driver內部將該值設置到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);
        }

    }
可用看到最後設置的是socket的soTimeout

實例

@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
        }
    }

超時錯誤輸出nginx

//部分數據輸出......
java.sql.SQLRecoverableException: IO 錯誤: 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
剛開始會有數據輸出,可是到了某個resultSet的next的時候,報了超時( close_or_fetch_from_next),這個超時指定的是當result.next方法觸發新的一批數據的拉取( 當一個fetchSize的數據消費完以後,接下來的next會觸發新一批數據的fetch)以後在timeout時間返回內沒有收到數據庫返回的數據。

oracle的jdbc默認的fetchSize爲10,也就是每一個fetch,若是超過指定時間沒接收到數據,則拋出timeout異常。spring

小結

jdbc的socketTimeout值的設置要很是當心,不一樣數據庫的jdbc driver設置不同,特別是使用不一樣鏈接池的話,設置也可能不盡相同。對於嚴重依賴數據庫操做的服務來講,很是有必要設置這個值,不然萬一網絡或數據庫異常,會致使服務線程一直阻塞在java.net.SocketInputStream.socketRead0。sql

  • 若是查詢數據多,則會致使該線程持有的data list不能釋放,至關於內存泄露,最後致使OOM
  • 若是請求數據庫操做不少且阻塞住了,會致使服務器可用的woker線程變少,嚴重則會致使服務不可用,nginx報504 Gateway Timeout

doc

相關文章
相關標籤/搜索