ExecuteReader에는 열려 있고 사용 가능한 연결이 필요합니다. 연결의 현재 상태는 연결 중입니다.


114

ASP.NET 온라인을 통해 MSSQL 데이터베이스에 연결하려고 할 때 두 명 이상의 사람이 동시에 연결하면 다음과 같은 메시지가 표시됩니다.

ExecuteReader에는 열려 있고 사용 가능한 연결이 필요합니다. 연결의 현재 상태는 연결 중입니다.

이 사이트는 내 localhost 서버에서 잘 작동합니다.

이것은 대략적인 코드입니다.

public Promotion retrievePromotion()
{
    int promotionID = 0;
    string promotionTitle = "";
    string promotionUrl = "";
    Promotion promotion = null;
    SqlOpenConnection();
    SqlCommand sql = SqlCommandConnection();

    sql.CommandText = "SELECT TOP 1 PromotionID, PromotionTitle, PromotionURL FROM Promotion";

    SqlDataReader dr = sql.ExecuteReader();
    while (dr.Read())
    {
        promotionID = DB2int(dr["PromotionID"]);
        promotionTitle = DB2string(dr["PromotionTitle"]);
        promotionUrl = DB2string(dr["PromotionURL"]);
        promotion = new Promotion(promotionID, promotionTitle, promotionUrl);
    }
    dr.Dispose();
    sql.Dispose();
    CloseConnection();
    return promotion;
}

무엇이 잘못되었는지 알 수 있으며 어떻게 해결해야합니까?

편집 : 잊지 마세요, 내 연결 문자열과 연결이 모두 정적입니다. 이것이 이유라고 생각합니다. 조언하십시오.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;

24
잠금 또는 예외 (너무 많은 열린 연결 등)를 생성하므로 ASP.NET과 같은 다중 스레딩 환경에서 공유 / 정적 연결을 사용하지 마십시오. DB-Class를 쓰레기통에 버리고 필요한 곳에 ado.net 객체를 만들고, 열고, 사용하고, 닫고, 폐기하십시오. using-statement도 살펴보십시오.
Tim Schmelter 2012 년

2
SqlOpenConnection (); 및 sql.ExecuteReader ();에 대해 자세히 설명해 주시겠습니까? 기능 ..?
ANKIT이 라

private void SqlOpenConnection () {try {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {throw ex; }}
구오 홍콩 임

@GuoHongLim : 나는 정적조차도 기본적으로 캐시conString 되기 때문에 성능 측면에서 아무것도 추가하지 않는다는 것을 잊었 습니다 (현재 응용 프로그램의 모든 구성 값).
Tim Schmelter 2012 년

... 알려지지 않게하기 위해 : 데이터베이스 트랜잭션 처리 / 작업 단위가 올바른지 확인하는 것은 독자를위한 연습으로 남겨집니다.
mwardm

답변:


226

애초에 댓글을 달아서 미안하지만 많은 사람들이 ADO.NET 기능을 DB-Class에 캡슐화하는 것이 현명 할 것이라고 생각하기 때문에 거의 매일 비슷한 댓글을 게시하고 있습니다 (저도 10 년 전). 대부분은 어떤 작업에 대해 새 개체를 만드는 것보다 더 빠르기 때문에 정적 / 공유 개체를 사용하기로 결정합니다.

이는 성능이나 고장 안전 측면에서 좋은 생각이 아닙니다.

Connection-Pool의 영역을 밀렵하지 마십시오.

ADO.NET이 ADO-NET Connection-Pool 에서 DBMS에 대한 기본 연결을 내부적으로 관리하는 데에는 좋은 이유가 있습니다 .

실제로 대부분의 응용 프로그램은 연결에 대해 하나 또는 몇 가지 다른 구성 만 사용합니다. 즉, 응용 프로그램 실행 중에 많은 동일한 연결이 반복적으로 열리고 닫힙니다. 연결 열기 비용을 최소화하기 위해 ADO.NET은 연결 풀링이라는 최적화 기술을 사용합니다.

연결 풀링은 새 연결을 열어야하는 횟수를 줄입니다. 풀러는 물리적 연결의 소유권을 유지합니다. 지정된 각 연결 구성에 대해 활성 연결 집합을 유지하여 연결을 관리합니다. 사용자가 연결에서 Open을 호출 할 때마다 풀러는 풀에서 사용 가능한 연결을 찾습니다. 풀링 된 연결을 사용할 수있는 경우 새 연결을 여는 대신 호출자에게 반환합니다. 응용 프로그램이 연결에서 Close를 호출하면 풀러는 연결을 닫는 대신 풀링 된 활성 연결 집합으로이를 반환합니다. 연결이 풀로 반환되면 다음 Open 호출에서 다시 사용할 수 있습니다.

따라서 실제로 연결이 생성, 열리고 닫히지 않기 때문에 연결을 생성, 열기 또는 닫기를 피할 이유가 없습니다. 이것은 연결 풀이 연결을 재사용 할 수 있는지 여부를 알 수있는 "유일한"플래그입니다. 그러나 연결이 "사용 중"인 경우 (연결 풀이 가정) 새 물리적 연결은 DBMS에 대한 개방형이어야하므로 매우 비용이 많이 듭니다.

따라서 성능 향상은 없지만 그 반대입니다. 지정된 최대 풀 크기 (기본값은 100)에 도달하면 예외가 발생합니다 (열린 연결이 너무 많음 ...). 따라서 이것은 성능에 엄청난 영향을 미칠뿐만 아니라 심각한 오류의 원인이 될뿐만 아니라 (트랜잭션을 사용하지 않고) 데이터 덤핑 영역이됩니다.

정적 연결을 사용하는 경우에도이 개체에 액세스하려는 모든 스레드에 대해 잠금을 생성합니다. ASP.NET은 본질적으로 다중 스레딩 환경입니다. 따라서 기껏해야 성능 문제를 일으키는 이러한 잠금에 대한 큰 기회가 있습니다. 실제로 조만간 여러 가지 예외가 발생할 것입니다 (예 : ExecuteReader에 열려 있고 사용 가능한 연결이 필요함 ).

결론 :

  • 연결이나 ADO.NET 개체를 전혀 재사용하지 마십시오.
  • 정적으로 / 공유하지 마십시오 (VB.NET에서)
  • 항상 생성, 열기 (연결의 경우), 사용, 닫기 및 필요한 곳에 폐기 (메소드의 fe)
  • 를 사용 using-statement묵시적으로 폐기하고 가까운 (연결의 경우)

이는 Connections에만 해당되는 것이 아닙니다 (가장 눈에 띄기는하지만). 구현하는 모든 객체 는 네임 스페이스 에서 IDisposable폐기되어야합니다 (가장 간단합니다 using-statement) System.Data.SqlClient.

위의 모든 것은 모든 객체를 캡슐화하고 재사용하는 사용자 정의 DB-Class에 반대합니다. 그것이 내가 그것을 쓰레기로 말한 이유입니다. 그것은 문제의 원인 일뿐입니다.


편집 : 다음은 retrievePromotion-method 의 가능한 구현입니다 .

public Promotion retrievePromotion(int promotionID)
{
    Promotion promo = null;
    var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString;
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        var queryString = "SELECT PromotionID, PromotionTitle, PromotionURL FROM Promotion WHERE PromotionID=@PromotionID";
        using (var da = new SqlDataAdapter(queryString, connection))
        {
            // you could also use a SqlDataReader instead
            // note that a DataTable does not need to be disposed since it does not implement IDisposable
            var tblPromotion = new DataTable();
            // avoid SQL-Injection
            da.SelectCommand.Parameters.Add("@PromotionID", SqlDbType.Int);
            da.SelectCommand.Parameters["@PromotionID"].Value = promotionID;
            try
            {
                connection.Open(); // not necessarily needed in this case because DataAdapter.Fill does it otherwise 
                da.Fill(tblPromotion);
                if (tblPromotion.Rows.Count != 0)
                {
                    var promoRow = tblPromotion.Rows[0];
                    promo = new Promotion()
                    {
                        promotionID    = promotionID,
                        promotionTitle = promoRow.Field<String>("PromotionTitle"),
                        promotionUrl   = promoRow.Field<String>("PromotionURL")
                    };
                }
            }
            catch (Exception ex)
            {
                // log this exception or throw it up the StackTrace
                // we do not need a finally-block to close the connection since it will be closed implicitely in an using-statement
                throw;
            }
        }
    }
    return promo;
}

이것은 연결 작업 패러다임을 제공하는 데 정말 유용합니다. 이 설명에 감사드립니다.
aminvincent

잘 쓰여졌 고, 많은 사람들이 우연히 발견 한 것에 대한 설명이며, 더 많은 사람들이 이것을 알았 으면합니다. (+1)
Andrew Hill

1
감사합니다. 제가 지금까지 읽은이 주제에 대한 가장 좋은 설명이라고 생각합니다. 매우 중요하고 많은 초보자들이 실수하는 주제입니다. 당신의 뛰어난 작문 능력에 대해 칭찬해야합니다.
Sasinosoft

@Tim Schmelter 다른 스레드에서 실행되는 쿼리가 제안 된 접근 방식을 사용하여 커밋 / 롤백을 위해 단일 트랜잭션을 활용하도록하려면 어떻게해야합니까?
geeko

1

며칠 전에이 오류를 발견했습니다.

제 경우에는 싱글 톤에서 트랜잭션을 사용했기 때문입니다.

.Net은 위에서 언급 한 것처럼 Singleton과 잘 작동하지 않습니다.

내 해결책은 다음과 같습니다.

public class DbHelper : DbHelperCore
{
    public DbHelper()
    {
        Connection = null;
        Transaction = null;
    }

    public static DbHelper instance
    {
        get
        {
            if (HttpContext.Current is null)
                return new DbHelper();
            else if (HttpContext.Current.Items["dbh"] == null)
                HttpContext.Current.Items["dbh"] = new DbHelper();

            return (DbHelper)HttpContext.Current.Items["dbh"];
        }
    }

    public override void BeginTransaction()
    {
        Connection = new SqlConnection(Entity.Connection.getCon);
        if (Connection.State == System.Data.ConnectionState.Closed)
            Connection.Open();
        Transaction = Connection.BeginTransaction();
    }
}

내 인스턴스에 HttpContext.Current.Items를 사용했습니다. 이 클래스 DbHelper 및 DbHelperCore는 내 클래스입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.