последовательность
Эта статья в основном знакомит с настройкой тайм-аута сокета jdbc.
категория времени ожидания jdbc
В основном это следующие категории
- transaction timeout
Установите время выполнения транзакции, которая может содержать несколько операторов
- statement timeout(
也相当于result set fetch timeout)
Установлено время ожидания выполнения оператора, то есть время ожидания драйвером завершения выполнения оператора и получения данных (
注意statement的timeout不是整个查询的timeout,只是statement执行完成并拉取fetchSize数据返回的超时,之后resultSet的next在必要的时候还会触发fetch数据,每次fetch的超时时间是单独算的,默认也是以statement设置的timeout为准)
- jdbc socket timeout
Период тайм-аута для операций чтения и записи сокета ввода-вывода jdbc установлен, чтобы предотвратить блокировку и ожидание драйвера из-за проблем с сетью или базой данных. (
建议比statement timeout的时间长)
- os socket timeout
Это настройка сокета уровня ОС (
如果jdbc socket timeout没有设置,而os级别的socket timeout有设置,则使用系统的socket timeout值).
Чем выше тайм-аут различных уровней выше, тем выше приоритет, то есть, если следующая конфигурация меньше, чем указанное выше значение конфигурации, тайм-аут будет активирован первым, что эквивалентно указанному выше значению конфигурации «недействительно».
jdbc socket timeout
Реализация драйвера jdbc для этих разных данных отличается
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.
- Установить по свойствам
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);
Обратите внимание, что переменные среды должны быть установлены до того, как соединение будет установлено.
- tomcat jdbc pool
Как правило, мы не используем соединение jdbc напрямую, а используем пул соединений. Поскольку пул tomcat jdbc — это пул соединений с базой данных, используемый Springboot по умолчанию, вот как установить его в пуле tomcat jdbc.
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();
}
Предположим, вы настроили префикс здесь как spring.datasource.write, тогда приведенная выше конфигурация становится
spring.datasource.write.connectionProperties=oracle.net.CONNECT_TIMEOUT=10000;oracle.jdbc.ReadTimeout=60000
Если oracle.jdbc.ReadTimeout не установлен, значение по умолчанию в драйвере равно 0
oracle.jdbc.ReadTimeout
Драйвер внутренне устанавливает это значение в переменную 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);
}
}
Доступно, чтобы увидеть, что последний набор - это 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
}
}
вывод ошибки тайм-аута
//部分数据输出......
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
В начале будет вывод данных, но когда они дойдут до следующего набора результатов, будет сообщено о тайм-ауте (
close_or_fetch_from_next), это время ожидания указывает, когда метод result.next инициирует получение нового пакета данных (当一个fetchSize的数据消费完之后,接下来的next会触发新一批数据的fetch), а затем данные, возвращаемые базой данных, не принимаются в течение времени ожидания.
По умолчанию fetchSize jdbc оракула равен 10, то есть для каждой выборки, если данные не будут получены после указанного времени, будет выдано исключение тайм-аута.
резюме
Установка значения socketTimeout для jdbc должна быть очень осторожной.Настройки драйвера jdbc для разных баз данных отличаются, особенно если используются разные пулы соединений, настройки также могут отличаться. Для служб, сильно зависящих от операций с базой данных, очень важно установить это значение, иначе в случае исключений сети или базы данных поток службы всегда будет заблокирован в java.net.SocketInputStream.socketRead0.
- Если есть много данных запросов, список данных, удерживаемый потоком, который не может быть выпущен, что эквивалентно утечке памяти, и, наконец, приводит к OOM
- Если запрошенная операция базы данных слишком велика и заблокирована, количество рабочих потоков, доступных серверу, будет уменьшено, а в тяжелых случаях служба будет недоступна, и nginx сообщит 504 Gateway Timeout.