【问题标题】:SQL Server connection timeouts and using statements with command and connection objectsSQL Server 连接超时和使用带有命令和连接对象的语句
【发布时间】:2016-06-13 01:19:57
【问题描述】:

请在回答之前阅读整个问题。我很抱歉,我似乎从不写简短的问题......

我支持在 Windows Small Business Server 2011 SP1 机器上运行 SQL Server 2008 R2 的 C# 内部 Web 应用程序。

我们最近遇到了很多 SQL 超时,这里是一个异常示例:

System.Web.HttpUnhandledException:引发了“System.Web.HttpUnhandledException”类型的异常。 ---> System.InvalidOperationException:超时已过期。在从池中获取连接之前超时时间已过。这可能是因为所有池连接都在使用中并且达到了最大池大小。 在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection) 在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection,DbConnectionFactory connectionFactory) 在 System.Data.SqlClient.SqlConnection.Open()

我检查了一些东西,其中之一是代码如何处理连接和关闭连接。我在其他线程中读到,在连接中使用 Using 语句就足够了,因为它“......将连接创建包装在 try .. finally 中,并将连接处理调用放在 finally 中”。即使发生异常,连接也会关闭。

所以,我同意并且多年来一直使用这种方法。即使在连接中使用 Using 语句,其他人也建议显式关闭连接。我认为这将是多余的......

然而,我的问题是关于命令对象。其他人为此应用程序编写了一个大型数据库方法库,并且他们(在所有数据库方法中)在 SqlConnection 对象 using 语句之前声明了 SqlCommand 对象。他们还在连接使用语句之前将连接对象分配给命令对象。

在连接使用语句中声明和使用命令对象是更好的做法吗,否则会导致 sql 连接超时(除非其他原因导致 sql 连接超时)?以这段代码为例:

    public Musician GetMusician(int recordId)
    {
        Musician objMusician = null;
        SqlConnection con = new SqlConnection(_connectionString);
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = con;
        cmd.CommandText = "selectMusician";
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@id", recordId);

        using (con)
        {
            con.Open();
            SqlDataReader reader = cmd.ExecuteReader();
            if (reader.HasRows)
            {
                reader.Read();
                objMusician = new Musician((int)reader["id"]);
                objMusician.Name = (string)reader["name"];
            }
        }

        if objMusician != null)
        {
            objMusician.Albums = Albums.GetAlbums((int)objMusician.ID);
            objMusician.Tours = Tours.GetTours((int)objMusician.ID);
            objMusician.Interviews = Interviews.GetInterviews((int)objMusician.ID);
        }
        return objMusician;
    }

还知道调用页面中包含尝试捕获,并且该页面将错误记录到我们的日志记录数据库中。我们让异常冒泡到页面上的调用方法,并在那里得到处理。

【问题讨论】:

    标签: c# sql sql-server connection-timeout


    【解决方案1】:

    您应该在完成连接后明确关闭它。您永远不会关闭任何连接,因此在达到连接池限制后,您将收到错误,直到您手动回收池或它自行循环。在 using 块内移动属性分配块并执行 con.Close(); cmd.Dispose();在归还您的 objMusician 之前:

    using (con)
    {
        con.Open();
        SqlDataReader reader = cmd.ExecuteReader();
        if (reader.HasRows)
        {
            reader.Read();
            objMusician = new Musician((int)reader["id"]);
            objMusician.Name = (string)reader["name"];
        }
    
        if objMusician != null)
        {
            objMusician.Albums = Albums.GetAlbums((int)objMusician.ID);
            objMusician.Tours = Tours.GetTours((int)objMusician.ID);
            objMusician.Interviews = Interviews.GetInterviews((int)objMusician.ID);
        }
    
        con.Close();
        cmd.Dispose();        
    
        return objMusician;
    }
    

    【讨论】:

    • Using 语句应该关闭连接。这不正确吗?
    • 另外,主要问题是,是否应该在连接使用块中声明和使用命令对象?这样做会不会有问题?
    • Dispose 关闭连接并将其返回到池中。
    • 不,可以在连接块内部或外部声明命令对象...您的问题是您没有处理对象,因此您只是将它们留在内存中并希望GC 会在您遇到超时之前解决问题。根据您描述的行为,GC 不会在此之前处理任何事情。
    【解决方案2】:

    不知道它是否会帮助您解决超时问题,但我一直将我的代码结构如下,并且没有遇到这个问题:

    using(var cmd = new SqlCommand())
    {
        using(var con = new SqlConnection(ConnectionString))
        {
            con.Open();
            cmd.Connection = con;
            cmd.CommandText = "selectMusician";
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@id", recordId);
            ...
        }
    }
    

    刚刚在 MSDN 上阅读,它说“使用完组件后调用 Dispose。Dispose 方法使组件处于不可用状态。调用 Dispose 后,您必须释放对该组件的所有引用,以便垃圾收集器可以回收组件占用的内存。”这意味着为了让 GC 立即收集连接,您必须在释放命令之前释放连接,否则连接会一直挂起,直到 GC 开始对其调用 Finalize。

    【讨论】:

    • 但这不就是 Using 语句的作用吗?他们应该为您处理处置。有人说无论如何都明确调用 Dispose,即使您在连接中使用 Using 语句?只是希望得到一个明确的答案。
    • 是的,using语句调用Dispose。我上面的观点是以正确的顺序获取它们,以便当连接的 using 语句处理连接时,命令(具有对连接的引用)在之后处理。这样 GC 将立即收集连接,而不是等到它开始调用 Finalize (这可能需要一段时间)。
    【解决方案3】:

    重构您的方法如下。您可能会遇到数据读取器对连接的引用但尚未处理的情况。

    public Musician GetMusician(int recordId)
    {
        Musician objMusician = null;
    
        using(SqlConnection con = new SqlConnection(_connectionString))
        {
            con.Open();
            using (SqlCommand cmd = new SqlCommand())
            {
                cmd.Connection = con;
                cmd.CommandText = "selectMusician";
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@id", recordId);
    
                using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                {
                    if (reader.HasRows)
                    {
                        reader.Read();
                        objMusician = new Musician((int) reader["id"]);
                        objMusician.Name = (string) reader["name"];
                    }
    
                    if objMusician != null)
                    {
                        objMusician.Albums = Albums.GetAlbums((int)objMusician.ID);
                        objMusician.Tours = Tours.GetTours((int)objMusician.ID);
                        objMusician.Interviews = Interviews.GetInterviews((int)objMusician.ID);
                    }
                }
            }
    
            return objMusician;
        }
    }
    

    【讨论】:

    • 我认为这是我见过的最好的结构,使用语句进行连接,然后是命令,然后是阅读器,并且没有明确的处置或关闭。 ExecuteReader 上的 CommandBehavior.CloseConnection 有什么作用?为什么有必要?如果好的做法,那么我会使用它,但以前没有见过。
    • 它确保在您完成阅读器后立即关闭连接(即,当它关闭/处理时)。见这里:msdn.microsoft.com/en-us/library/…
    • 查看我最近在其他项目中的一些代码,我一直在使用与上面相同的结构,但在 ExecuteReader 上没有 CommandBehavior.CloseConnection。不过,我想我会用它来更新我的结构,作为一种新的改进......
    猜你喜欢
    • 2012-05-02
    • 1970-01-01
    • 2011-02-14
    • 2017-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-14
    • 2010-12-20
    相关资源
    最近更新 更多