从ComboPooledDataSource获取的连接偶尔已经签出

时间:2013-09-11 00:05:48

标签: jdbc c3p0

我发现当多个线程从ComboPooledDataSource的单个共享实例请求连接时,有时会从已经在使用中的池中返回连接。是否有配置设置或其他方法来确保当前检出的连接不会再次签出?

package stress;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

public class StressTestDriver
{
    private static final String _host = "";
    private static final String _port = "3306";
    private static final String _database = "";
    private static final String _user = "";
    private static final String _pass = "";

    public static void main(String[] args)
    {
        new StressTestDriver();
    }

    StressTestDriver()
    {
        ComboPooledDataSource cpds = new ComboPooledDataSource();

        try
        {
            cpds.setDriverClass( "com.mysql.jdbc.Driver" );

            String connectionString =  "jdbc:mysql://" + _host + ":" + _port + "/"
                    + _database;

            cpds.setJdbcUrl( connectionString );
            cpds.setMaxPoolSize( 15 );
            cpds.setMaxIdleTime( 100 );
            cpds.setAcquireRetryAttempts( 1 );
            cpds.setNumHelperThreads( 3 );
            cpds.setUser( _user );
            cpds.setPassword( _pass );
        }
        catch( PropertyVetoException e )
        {
            e.printStackTrace();
            return;
        }

        write("BEGIN");
        try
        {
            for(int i=0; i<100000; ++i)
                doConnection( cpds );
        }
        catch( Exception ex )
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                DataSources.destroy( cpds );
            }
            catch( SQLException e )
            {
                e.printStackTrace();
            }
        }
        write("END");
    }

    void doConnection( final ComboPooledDataSource cpds )
    {
        Thread[] threads = new Thread[ 10 ];
        final Set<String> set = new HashSet<String>(threads.length);

        Runnable runnable = new Runnable()
        {
            public void run()
            {
                Connection conn = null;

                try
                {
                    conn = cpds.getConnection();

                    synchronized(set)
                    {
                        String toString = conn.toString();

                        if( set.contains( toString ) )
                            write("In-use connection: " + toString);
                        else
                            set.add( toString );
                    }

                    conn.close();
                }
                catch( Exception e )
                {
                    e.printStackTrace();
                    return;
                }
            }
        };

        for(int i=0; i<threads.length; ++i)
        {
            threads[i] = new Thread( runnable );
            threads[i].start();
        }

        for(int i=0; i<threads.length; ++i)
        {
            try
            {
                threads[i].join();
            }
            catch( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
    }

    private static void write(String msg)
    {
        String threadName = Thread.currentThread().getName();
        System.err.println(threadName + ": " + msg);
    }
}

2 个答案:

答案 0 :(得分:0)

我在我的环境中运行压力测试。它在没有输出的情况下终止。

也就是说,使用toString()输出作为身份的标记对我来说有点不可靠。在Object.toString()中编码的哈希码不保证是唯一的,并且您正在生成批量。你可能只是看到了碰撞。

特别是,您关注的场景将代表一种令人惊讶且令人困惑的错误。您应该看到com.mchange.v2.c3p0.impl.NewProxyConnection的实例。这些实例不会在池中签入和签出 - 它们是一次性使用,包装底层数据库Connection的一次性代理。当你调用getConnection()时,它们用new构造,在你工作时保持与PooledConnection对象相关联,然后当你调用close()时由c3p0库取消引用,在客户端代码取消引用后进行垃圾回收。如果以某种方式在同一个底层PooledConnection上调用getConnection(),那将是一个c3p0错误,而c3p0会发出警告。你没有看到任何像......

  

c3p0 - 呃哦...当PooledConnection调用getConnection()时   它已经为客户端提供了尚未安装的连接   已经关闭。这可能表示连接池中存在错误!!!

你是吗?

我将验证您是否只是看到NewProxyConnection.toString()的冲突。使用映射而不是集合,保持对close()ed连接实例的引用作为值,当您看到String键的冲突时,请与==一起检查新Connection是否与您的值中的值相同地图。如果你发现它们是同一个实例,我会感到很惊讶。 (我希望我能重现这个问题,以便我自己验证这一点。)

答案 1 :(得分:0)

我添加了代码以获取Oracle和MS SQL Server的连接ID / @@ SPID,并且每当toString值相同时,连接ID总是不同的,因此c3p0不是罪魁祸首:

94843 - pool-1-thread-12 - :onCheckOut调用:com.microsoft.sqlserver.jdbc.SQLServerConnection@5e65d9 - H:6186457 - P :: 57
94843 - pool-1-thread-13 - :onCheckOut调用:com.microsoft.sqlserver.jdbc.SQLServerConnection@170a25e - H:24158814 - P :: 55
94843 - pool-1-thread-1 - :onCheckOut调用:com.microsoft.sqlserver.jdbc.SQLServerConnection@5e65d9 - H:6186457 - P :: 54

19172 - pool-1-thread-11 - :onCheckOut调用:oracle.jdbc.driver.T4CConnection@2475ca - H:2389450 - P :: 6564
19172 - pool-1-thread-31 - :onCheckOut调用:oracle.jdbc.driver.T4CConnection@2475ca - H:2389450 - P :: 6448

我将不得不考虑修改现有代码。谢谢你的帮助史蒂夫!你指出了我正确的方向。