即使明确设置 PreparedStatement 也不会超时

Posted

技术标签:

【中文标题】即使明确设置 PreparedStatement 也不会超时【英文标题】:PreparedStatement won't ever timeout even if explicitly set 【发布时间】:2018-02-09 10:18:51 【问题描述】:

我正在尝试模拟一个场景,即我的服务失去与数据库的连接并且无法通过阻止与 iptables 的连接来执行 INSERT,但我无法使 executeQuery() 方法超时。

我所做的是像statement.setQueryTimeout(5) 那样为 PreparedStatement 设置超时。这是代码。

HikariConfig config = new HikariConfig();

config.setJdbcUrl("jdbc:mysql://db-url/db");
config.setUsername("user");
config.setPassword("passwd");

config.setMaximumPoolSize(10);
config.setAutoCommit(false);
config.setConnectionTimeout(5000);
config.setDriverClassName("com.mysql.jdbc.Driver");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("autoReconnect", "true");

final HikariDataSource pool = new HikariDataSource(config);

final String query = "INSERT INTO xtable VALUES (?, ?, ?, ?, ?)";

try ( Connection connection = pool.getConnection() )

    try ( PreparedStatement statement = connection.prepareStatement(query) )
    
        // this is what I expect to work
        statement.setQueryTimeout(5);

        for ( Info info : infos )
        
            statement.setString(1, info.getValue1());
            statement.setString(2, info.getValue2());
            statement.setString(3, info.getValue3());
            statement.setString(4, info.getValue4());
            statement.setString(5, info.getValue5());

            try
            
                System.out.println("Waiting");
                Thread.sleep(5000);
                // I use this sleep to ban the database url with iptables
                // to simulate a disconnection
                System.out.println("Waited");
            
            catch ( InterruptedException e )
            
                e.printStackTrace();
            

            System.out.println("Before executeQuery");
            statement.executeQuery();
            // I assumed that this would timeout after 5 seconds
            // But it never reaches the next System.out.print
            System.out.println("After executeQuery");
        
    

    System.out.println("Before commit");
    connection.commit();
    System.out.println("After commit");

catch ( SQLException e )

    log.error("Couldn't execute query", e);

输出将是:

Waiting
Waited
Before executeQuery

然后它永远挂起......我该怎么做才能让它抛出异常?

【问题讨论】:

【参考方案1】:

打电话给Connection.setNetworkTimeout()试一试

private final static Executor immediateExecutor = Runnable::run;

try ( Connection connection = pool.getConnection() ) 
   int timeout = connection.getNetworkTimeout();
   connection.setNetworkTimeout(immediateExecutor, TimeUnit.SECONDS.toMillis(5));
   ...
   try (PreparedStatement...) 
      ...
   
   finally 
      connection.setNetworkTimeout(timeout);
   

finally 
   ...

您遇到了未经确认的 TCP 流量,如果未设置网络超时,可能会挂起连接。

【讨论】:

它似乎工作得很好,谢谢!有两件事困扰我:一,在 connection.close() 中,它抛出未捕获的异常,因为发生网络超时后无法关闭它。另外:我每次都必须设置这个超时吗?如果我有一个 Hikari 池,那么每次需要连接时都设置它似乎是无稽之谈,对吧?我的意思是,主要是因为我会继续创建执行器。 另一个选项是在驱动程序级别设置网络超时 - 全局。在这种情况下,我建议将其设置为最长查询时间的 3 倍。 最后一点。您不需要继续创建 Executors。如上面的示例,您可以(应该)创建一个静态 Executor,并且该 executor(在 MySQL 的情况下)应该立即执行 Runnable,而不是排队或传递给另一个线程。 好建议,我会试试的。我无法在 jdbc 驱动程序级别设置网络超时。谢谢【参考方案2】:

你总是可以:

try
     preparedstatement = connection.prepareStatement(query);
     preparedstatement.setQueryTimeout(seconds);

而不是你所做的。也许这样会更好。 同样的事情也发生在:

Connection connection = pool.getConnection() 

【讨论】:

那会有什么不同? @fcasanova 调试时更容易判断哪一行在哪个值之后抛出了哪个异常或错误。这不是一个答案,而是一个寻找它为什么不起作用的建议

以上是关于即使明确设置 PreparedStatement 也不会超时的主要内容,如果未能解决你的问题,请参考以下文章

即使 PreparedStatement 仅使用一次,ResultSet 关闭错误后也不允许操作

Swift:命令中心不起作用,即使我明确设置按钮启用?

为啥即使没有争用,我的 Oracle PreparedStatement 有时也不会返回?

将 null 设置为 PreparedStatement

Java PreparedStatement将IN参数设置为int数组[重复]

IN子句效率中的preparedstatement设置值