ExecuteReader ต้องการการเชื่อมต่อแบบเปิดและพร้อมใช้งาน สถานะปัจจุบันของการเชื่อมต่อคือกำลังเชื่อมต่อ


114

เมื่อพยายามเชื่อมต่อกับฐานข้อมูล MSSQL ผ่าน ASP.NET ออนไลน์ฉันจะได้รับสิ่งต่อไปนี้เมื่อมีคนสองคนขึ้นไปเชื่อมต่อพร้อมกัน:

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 ของคุณไปที่ถังขยะและสร้างเปิดใช้ปิดทิ้งวัตถุออบเจ็กต์ในที่ที่คุณต้องการ ดูคำสั่งการใช้ด้วย
Tim Schmelter

2
คุณสามารถให้รายละเอียดเกี่ยวกับ SqlOpenConnection (); และ sql.ExecuteReader (); ฟังก์ชัน? ..
ankit rajput

โมฆะส่วนตัว SqlOpenConnection () {ลอง {conn = new SqlConnection (); conn.ConnectionString = conString; conn.Open (); } catch (SqlException ex) {throw ex; }}
Guo Hong Lim

@GuoHongLim: ฉันลืมที่จะพูดถึงว่าแม้แต่สแตติกก็conStringไม่เพิ่มอะไรเลยในแง่ของประสิทธิภาพเนื่องจากมันถูกแคชไว้โดยค่าเริ่มต้นอยู่ดี (เป็นค่าการกำหนดค่าทั้งหมดสำหรับแอปพลิเคชันปัจจุบัน)
Tim Schmelter

... และเพื่อให้เป็นที่รู้จัก - ไม่รู้จัก: การทำให้มั่นใจว่าคุณได้รับการจัดการธุรกรรมฐานข้อมูล / หน่วยการทำงานที่ถูกต้องถือเป็นแบบฝึกหัดสำหรับผู้อ่าน
mwardm

คำตอบ:


227

ขออภัยสำหรับการแสดงความคิดเห็นในตอนแรก แต่ฉันโพสต์ความคิดเห็นที่คล้ายกันเกือบทุกวันเนื่องจากหลายคนคิดว่าการห่อหุ้มฟังก์ชัน ADO.NET ไว้ใน DB-Class จะเป็นการดี (ฉันเมื่อ 10 ปีก่อน) ส่วนใหญ่พวกเขาตัดสินใจที่จะใช้วัตถุคงที่ / ใช้ร่วมกันเนื่องจากดูเหมือนว่าจะเร็วกว่าการสร้างวัตถุใหม่สำหรับการกระทำใด ๆ

นั่นไม่ใช่ความคิดที่ดีในแง่ของความเหมาะสมหรือในแง่ของความล้มเหลว - ความปลอดภัย

อย่ารุกล้ำอาณาเขตของ Connection-Pool

มีเหตุผลที่ดีว่าทำไม ADO.NET จึงจัดการการเชื่อมต่อพื้นฐานไปยัง DBMS ภายในADO-NET Connection-Pool :

ในทางปฏิบัติแอปพลิเคชันส่วนใหญ่จะใช้การกำหนดค่าที่แตกต่างกันเพียงหนึ่งหรือสองสามอย่างสำหรับการเชื่อมต่อ ซึ่งหมายความว่าในระหว่างการเรียกใช้งานแอปพลิเคชันการเชื่อมต่อที่เหมือนกันหลาย ๆ ครั้งจะถูกเปิดและปิดซ้ำ ๆ เพื่อลดต้นทุนในการเปิดการเชื่อมต่อ ADO.NET ใช้เทคนิคการเพิ่มประสิทธิภาพที่เรียกว่าการรวมการเชื่อมต่อ

การรวมการเชื่อมต่อช่วยลดจำนวนครั้งที่ต้องเปิดการเชื่อมต่อใหม่ ตัวยึดจะรักษาความเป็นเจ้าของการเชื่อมต่อทางกายภาพ จัดการการเชื่อมต่อโดยการรักษาชุดการเชื่อมต่อที่ใช้งานอยู่สำหรับการกำหนดค่าการเชื่อมต่อแต่ละรายการ เมื่อใดก็ตามที่ผู้ใช้เรียกใช้ Open บนการเชื่อมต่อ pooler จะค้นหาการเชื่อมต่อที่พร้อมใช้งานในพูล หากมีการเชื่อมต่อแบบรวมระบบจะส่งกลับไปยังผู้โทรแทนที่จะเปิดการเชื่อมต่อใหม่ เมื่อแอปพลิเคชันเรียกปิดบนการเชื่อมต่อตัวควบคุมจะส่งกลับไปยังชุดการเชื่อมต่อที่ใช้งานร่วมกันแทนที่จะปิด เมื่อการเชื่อมต่อถูกส่งกลับไปที่พูลก็พร้อมที่จะใช้ซ้ำในการโทรแบบเปิดครั้งถัดไป

เห็นได้ชัดว่าไม่มีเหตุผลใดที่จะหลีกเลี่ยงการสร้างเปิดหรือปิดการเชื่อมต่อเนื่องจากจริงๆแล้วพวกเขาไม่ได้สร้างเปิดและปิดเลย นี่เป็นแฟล็ก "เท่านั้น" สำหรับพูลการเชื่อมต่อเพื่อให้ทราบเมื่อสามารถใช้การเชื่อมต่อซ้ำได้หรือไม่ แต่เป็นแฟล็กที่สำคัญมากเพราะหากการเชื่อมต่อ "ใช้งานอยู่" (พูลการเชื่อมต่อจะถือว่า) การเชื่อมต่อทางกายภาพใหม่จะต้องเปิดขึ้นกับ DBMS ซึ่งมีราคาแพงมาก

ดังนั้นคุณจึงไม่ได้รับการปรับปรุงประสิทธิภาพ แต่ตรงกันข้าม หากถึงขนาดพูลสูงสุดที่ระบุ (100 คือค่าเริ่มต้น) คุณจะได้รับข้อยกเว้น (การเชื่อมต่อที่เปิดมากเกินไป ... ) ดังนั้นสิ่งนี้จะไม่เพียงส่งผลกระทบต่อประสิทธิภาพอย่างมาก แต่ยังเป็นแหล่งที่มาของข้อผิดพลาดที่น่ารังเกียจและ (โดยไม่ใช้ธุรกรรม) พื้นที่ทิ้งข้อมูล

หากคุณใช้การเชื่อมต่อแบบคงที่คุณกำลังสร้างล็อกสำหรับทุกเธรดที่พยายามเข้าถึงวัตถุนี้ ASP.NET เป็นสภาพแวดล้อมแบบมัลติเธรดโดยธรรมชาติ ดังนั้นจึงมีโอกาสที่ดีสำหรับการล็อกเหล่านี้ซึ่งทำให้เกิดปัญหาด้านประสิทธิภาพที่ดีที่สุด ไม่ช้าก็เร็วคุณจะได้รับข้อยกเว้นต่างๆมากมาย (เช่นExecuteReaderของคุณต้องการการเชื่อมต่อแบบเปิดและพร้อมใช้งาน )

สรุป :

  • อย่าใช้การเชื่อมต่อซ้ำหรือวัตถุ ADO.NET ใด ๆ เลย
  • อย่าทำให้เป็นแบบคงที่ / แชร์ (ใน VB.NET)
  • สร้างเปิด (ในกรณีของ Connections) ใช้ปิดและกำจัดทิ้งในที่ที่คุณต้องการเสมอ (fe ในวิธีการ)
  • ใช้using-statementเพื่อกำจัดและปิด (ในกรณีของ Connections) โดยปริยาย

นั่นเป็นความจริงไม่เพียง แต่สำหรับ 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;
}

นี่เป็นประโยชน์อย่างยิ่งในการให้ paradigma ทำงานที่เชื่อมต่อ ขอบคุณสำหรับคำอธิบายนี้
aminvincent

เขียนได้ดีคำอธิบายสำหรับบางสิ่งที่หลายคนค้นพบโดยบังเอิญและฉันหวังว่าจะมีคนรู้เรื่องนี้มากขึ้น (+1)
Andrew Hill

1
ขอบคุณครับผมคิดว่านี่เป็นคำอธิบายที่ดีที่สุดในเรื่องนี้ที่ผมเคยอ่านเรื่องที่สำคัญมากและมือใหม่หลายคนก็เข้าใจผิด ฉันต้องขอชมเชยคุณสำหรับความสามารถในการเขียนที่ยอดเยี่ยมของคุณ
Sasinosoft

@ Tim Schmelter ฉันจะทำให้การสืบค้นของฉันทำงานบนเธรดที่แตกต่างกันโดยใช้ธุรกรรมเดียวในการดำเนินการ / ย้อนกลับโดยใช้แนวทางที่คุณแนะนำได้อย่างไร
geeko

1

ฉันพบข้อผิดพลาดนี้เมื่อสองสามวันก่อน

ในกรณีของฉันมันเป็นเพราะฉันใช้ธุรกรรมกับ Singleton

.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.CurrentItems สำหรับอินสแตนซ์ของฉัน คลาส DbHelper และ DbHelperCore นี้เป็นคลาสของฉันเอง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.