传统的 DB 单例连接效果不佳

Posted

技术标签:

【中文标题】传统的 DB 单例连接效果不佳【英文标题】:Traditional DB singleton connection works poorly 【发布时间】:2013-12-21 06:21:33 【问题描述】:

我在我的 java 应用程序中使用单例数据库连接,这是我的连接管理器类的代码:

public abstract class DatabaseManager 
    //Static instance of connection, only one will ever exist
        private static Connection connection = null;    
        private static String dbName="SNfinal";
        //Returns single instance of connection
        public static Connection getConnection()       
            //If instance has not been created yet, create it
            if(DatabaseManager.connection == null)
                initConnection();
            
            return DatabaseManager.connection;
           
        //Gets JDBC connection instance
        private static void initConnection()           
            try        
                Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
                   String connectionUrl = "jdbc:sqlserver://localhost:1433;" +
                      "databaseName="+dbName+";integratedSecurity=true";

                DatabaseManager.connection =
                             DriverManager.getConnection(connectionUrl);        
            
            catch (ClassNotFoundException e)       
                System.out.println(e.getMessage());
                System.exit(0);
            
            catch (SQLException e)         
                System.out.println(e.getMessage());
                System.exit(0);
            
            catch (Exception e)        
                   
        
    public static ResultSet executeQuery(String SQL, String dbName)
    
        ResultSet rset = null ;
        try 
               Statement st = DatabaseManager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
               rset = st.executeQuery(SQL);
               //st.close();
        
        catch (SQLException e) 
            System.out.println(e.getMessage());
            System.exit(0);
        
        return rset;
     

    public static void executeUpdate(String SQL, String dbName)
    
        try 
               Statement st = DatabaseManager.getConnection().createStatement();
               st.executeUpdate(SQL);
               st.close();
        

        catch (SQLException e) 
            System.out.println(e.getMessage());
            System.exit(0);
        
     

问题是我的代码在开始时运行良好,但是当时间过去时它变得非常慢。是什么导致了这个问题,我该如何解决? 在开始时,我的应用程序每秒处理大约 20 个查询,运行 1 小时后达到每秒 10 个查询,运行 3 天后达到每 10 秒 1 个查询! P.S:我的应用程序是一个单用户应用程序,它通过数据库进行许多查询。 P.S:这是我在 eclipse.ini 中的 JVM 参数:

--launcher.XXMaxPermSize
512M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
512m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.6
-Xms500m
-Xmx4G
-XX:MaxHeapSize=4500m

不幸的是数据库是远程的,我没有任何监控访问权限来了解那里发生了什么。

这是我的用法示例:

String count="select count(*) as counter from TSN";
ResultSet rscount=DatabaseManager.executeQuery(count, "SNfinal");
if(rscount.next()) 
    numberofNodes=rscount.getInt("counter");

【问题讨论】:

在课堂上使用@singleton 即使对于单用户应用程序,我也建议使用连接池(例如 BoneCP)和一个非常小的池。只需获得一个连接并在完成后关闭它,池会处理检查连接,如果它已打开很长时间则替换它等。它还可以保护您的代码免受以前执行中未提交的事务等的影响。 实际上我需要尽可能快地运行查询。我第一次使用 c3p0,但它对我的应用程序的性能确实不如传统的单例连接(慢了大约 90%!) 为什么您的st.close() 被注释掉了?您是否正确关闭ResultSets?当涉及到正确的资源处理时,JDBC 并不容易使用。我建议你使用像 spring-jdbc 和它的 JdbcTemplate. 这样的包装库 你关于连接池很慢的论点似乎有点奇怪。当涉及到数据库通信时,一些方法调用开销是微不足道的。但我同意,如果您的用例不需要连接池,则不需要它。您可能缺少的唯一优势是连接验证。 【参考方案1】:

是什么导致了这个问题,我该如何解决?

您在这里遇到的主要问题是executeQuery() 方法。 你没有关闭Statement,我想你已经评论了st.close()这一行,因为你需要打开ResultSet 用于进一步处理。 我可以看到您的想法是避免在您的应用程序中看到重复的 JDBC 代码,但这不是正确的方法。

规则是:关闭ResultSet,然后关闭Statement, 否则,您将无法正确释放资源,并且会遇到您所描述的问题。

Here 你可以找到关于如何正确关闭资源的很好的解释(请记住,在你的情况下你不需要 关闭连接)

编辑: 一个例子可以是

try
Statement st = DatabaseManager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rsCount = st.executeQuery(count);         //count="select count(*) as counter from TSN";
if(rsCount.next()) 
    numberofNodes=rscount.getInt("counter");

 catch (SQLException e) 
    //log exception
 finally 
    rsCount.close();
    st.close();

【讨论】:

我无法关闭结果集或语句,因为我要运行多个查询。我一关闭它们就出错了。 分配的内存没有满,所以不可能是内存问题。我认为它是由其他原因引起的,但我不知道是什么。 我坚持认为您的 executeQuery() 方法设计得不好。尝试进行一些重构以允许您关闭该语句。我不知道您的应用程序代码,但它应该很容易替换每个 DatabaseManager.executeQuery() 方法调用,用于允许您关闭的树语句(1-> 获取连接、2-> 创建语句、3-> 获取结果集) ResultSet,尤其是 Statement。 我添加了一个如何调用此方法的示例,请您解释一下您对这个示例的想法吗? 在我的所有代码中更改它确实非常耗时,因此我选择了 Summit 解决方案。我想知道为什么该解决方案完全解决了我的问题!【参考方案2】:

您应该考虑使用断开连接的结果集,例如 CachedRowSet http://docs.oracle.com/javase/1.5.0/docs/api/javax/sql/rowset/CachedRowSet.html

public static ResultSet executeQuery(String SQL, String dbName)

    CachedRowSetImpl crs = new CachedRowSetImpl();
    ResultSet rset = null ;
    Statement st = null;
    try 
           st = DatabaseManager.getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
           rset = st.executeQuery(SQL);
           crs.populate(rset);
    
    catch (SQLException e) 
        System.out.println(e.getMessage());
        System.exit(0);
    finally
        rset.close();
        st.close();
    
    return crs;
 

CachedRowSet 实现了 ResultSet,所以它的行为应该像一个 ResultSet。

http://www.onjava.com/pub/a/onjava/2004/06/23/cachedrowset.html

除了这些更改之外,我建议您使用池数据源来获取连接并关闭它们,而不是保持一个打开的连接。

http://brettwooldridge.github.io/HikariCP/

或者,如果您不是 java7、bonecp 或 c3po。

编辑:

为了回答您的问题,这解决了您的问题,因为 CachedRowSetImpl 在使用时不会保持与数据库的连接。 这允许您在填充CachedRowSetImpl 后关闭ResultsetStatement

希望能回答您的问题。

【讨论】:

请问为什么这个解决方案解决了我的问题?!我在这两种方法中都监控了 jvm 资源的使用情况。从资源管理的角度来看,似乎默认方法更好,所以不会是资源使用率高造成的!【参考方案3】:

    虽然连接管理器会自动关闭StatementResultset,但最好立即关闭。

    您的代码中没有任何其他内容会影响您的单线程任务,所以我敢打赌您的数据库中一定有问题。尝试找出是否有任何数据库锁定或错误的列索引。还要看看数据库查询状态,找出瓶颈在哪里。

【讨论】:

如果是数据库问题,为什么要随着时间的推移而延长? 因为你的数据集一直在增长,所以查询 100M 记录肯定比 100K 记录要慢。 不,它没有增长,我逐条记录查询过程!

以上是关于传统的 DB 单例连接效果不佳的主要内容,如果未能解决你的问题,请参考以下文章

PHP开发APP接口

Try/Catch 在调试中效果很好,但在 .exe 中效果不佳

Scala - 为使用数据库连接扩展特征/类的对象/单例编写单元测试

Node.JS中使用单例封装MongoDB

小米1S无线网连接时老是出现暂时关闭状态不佳的链接

单例模式