【问题标题】:C# MySQL connection pool limits, and cleaning up connectionsC# MySQL 连接池限制和清理连接
【发布时间】:2011-12-01 17:28:04
【问题描述】:

我有一个简单的数据库管理器类(一个比它的能力更宏大的名字):

class DbManager
{
    private MySqlConnectionStringBuilder _connectionString;

    public DbManager()
    {
        _connectionString = new MySqlConnectionStringBuilder();
        _connectionString.UserID = Properties.Database.Default.Username;
        _connectionString.Password = Properties.Database.Default.Password;
        _connectionString.Server = Properties.Database.Default.Server;
        _connectionString.Database = Properties.Database.Default.Schema;
        _connectionString.MaximumPoolSize = 5;
    }


    public MySqlConnection GetConnection()
    {
        MySqlConnection con = new MySqlConnection(_connectionString.GetConnectionString(true));
        con.Open();
        return con;
    }

}

然后我在其他地方有另一个类,它表示其中一个表中的记录,我像这样填充它:

class Contact
{
    private void Populate(object contactID)
    {
        using (OleDbConnection con = DbManager.GetConnection())
        {
            string q = "SELECT FirstName, LastName FROM Contacts WHERE ContactID = ?";

            using (OleDbCommand cmd = new OleDbCommand(q, con))
            {
                cmd.Parameters.AddWithValue("?", contactID);

                using (OleDbDataReader reader = cmd.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        reader.Read();
                        this.FirstName = reader.GetString(0);
                        this.LastName = reader.GetString(1);
                        this.Address = new Address();
                        this.Address.Populate(ContactID)
                    }
                }
            }
        }
    }
}


class Address
{
    private void Populate(object contactID)
    {
        using (OleDbConnection con = DbManager.GetConnection())
        {
            string q = "SELECT Address1 FROM Addresses WHERE ContactID = ?";

            using (OleDbCommand cmd = new OleDbCommand(q, con))
            {
                cmd.Parameters.AddWithValue("?", contactID);

                using (OleDbDataReader reader = cmd.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        reader.Read();
                        this.Address1 = reader.GetString(0);
                    }
                }
            }
        }
    }
}

现在我认为所有using 语句都会确保连接完成后返回到池中,以供下次使用,但我有一个循环可以创建数百个这样的联系人并填充它们,似乎连接没有被释放。

连接、命令和阅读器都在自己的using语句中。

【问题讨论】:

  • 我认为这不在多线程应用程序中?这显然有可能一次尝试使用多个这些东西......这是您创建数据库连接的唯一地方吗?它们不可能泄漏到其他地方吗?
  • 我想我一定对连接重用的建议感到困惑。它多线程的:将同时调用多个Populate,但我的印象是我不应该有一个共享的OleDbConnection,而是应该根据需要创建新的。
  • 只是为了检查发生了什么,如果您在 using 块的末尾显式调用 con.Dispose() 会发生什么?
  • 嗯,我认为这让我遇到了一个问题。 populate 调用另一个子对象的填充。如果我把它注释掉,那很好。子对象的填充几乎与那个完全一样。
  • @Cylindric:我添加了一个答案来解释我认为正在发生的事情。使用游泳池是个好主意,但本质上你需要确保游泳池足够大以应付。 :)

标签: c# mysql


【解决方案1】:

如果应用程序是多线程的,那么您可能会同时运行 10 个线程。每个都需要自己的连接,但如果您将池大小限制为 5,那么您的第 6 个线程将无法从池中获取连接。

您可能会以某种方式限制线程,但我建议您显着增加应用程序池的大小,以确保您的可用连接数多于线程数。作为一个指标,默认大小(通常对大多数人来说已经足够)是 100。

此外,如果您在 using 块内有任何递归(例如再次调用 populate),正如您在 cmets 中所指出的那样,而不是上面的代码,那么您将遇到更多问题。

如果您在 using 块内调用 populate,那么您将打开并使用来自父级的连接(因此不可重用),然后子调用将打开另一个连接。如果这种情况只发生几次,您的连接分配就会用完。

为了防止这种情况,您希望将辅助 Populate 调用移出 using 块。最简单的方法不是循环调用每个 ID 的记录集调用填充,而是将 ID 添加到列表中,然后在关闭连接后为所有新 ID 执行填充。

或者,您可以懒惰地评估诸如地址之类的东西。将 addressID 存储在一个私有字段中,然后使 Address 成为一个属性,检查其支持字段(不是 addressID)是否已填充,如果没有,则使用 AddressID 查找它。这样做的好处是,如果您从不查看地址,您甚至不会进行数据库调用。根据数据的使用,这可能会为您节省大量的数据库命中,但如果您确实使用了所有细节,那么它只会将它们转移,可能会将它们分散得更多,这可能有助于提高性能,或者可能根本没有任何区别. :)

一般而言,对于数据库访问,我会尝试将所有数据取出并尽快关闭连接,最好是在对数据进行任何复杂计算之前。另一个很好的理由是,根据您的数据库查询等,您可能会锁定您正在使用查询访问的表,这可能会导致数据库方面的锁定问题。

【讨论】:

  • 我正在限制我的线程以稍微限制远程数据库连接,但我认为我可能没有考虑到每个主要“填充”需要大约三个连接,一个用于父级的事实,一个用于查找孩子,一个用于填充每个孩子。所以connections = 3*threads
  • 我们的编辑发生了冲突。似乎评论不是线程安全的......你似乎是正确的,我认为它正在爆炸连接数。
  • 如果你仍然限制你的线程,那么我不会担心限制你的线程太多。如果您只拥有五个线程,那么在池中拥有 50 个连接将无关紧要。它不会预先创建所有 50 个,它只会根据需要创建它们并保留它们一段时间。如果有这样的不可预见的情况,那么你就有了缓冲。如果你想对你的数据库连接非常严格,那么我相信有办法在获取连接时进行某种锁定,这样一个线程可以等待直到它得到一个 con
  • 是的,我只将 connection-max 设置为 5,所以它会更快地中断。在生产中它需要同步大约 80k 记录,所以我不希望它在我不看的时候爬到 max
【解决方案2】:

我建议进行一些更改:

1) 在 using 语句终止之前显式关闭连接。查看 OleDbConnection(以及 DbConnection 和 DbConnectionInternal)的源代码让我相信连接没有在 dispose 时显式关闭,只是被放弃了。

2) 修改 Address.Populate 以接受一个连接参数,这样当您只需要一个并从联系人传递打开的连接时,您就不必创建两个打开的连接。如果在连接不可用时会调用 Address.Populate 的情况,您可以在使用连接对象调用 Populate 重载之前在 Address 中创建打开连接的 Populate 的重载版本。 更新 Chris 的建议:我假设人们会知道在这种情况下关闭开放式阅读器,但事实并非如此。如果使用此方法,则必须在将打开的连接传递给该方法之前显式关闭任何打开的读取器。

更新

刚刚验证了建议 #1。来自MSDN Documentation

"如果 DbConnection 超出范围,则不会关闭。因此,您必须通过调用 Close 或 Dispose 显式关闭连接,它们在功能上是等效的。如果连接池值 Pooling 设置为 true 或 yes,则此也释放物理连接。”

【讨论】:

  • 选项 2 不会导致抱怨连接已在使用中吗?我有一个记忆,一次只能由一件事使用连接......
  • 有趣的冲突信息。 DbConnection 的 MSDN 文档没有提及这一点,close 的示例也没有提及。
  • @Chris:只要您清理之前的活动(即关闭打开的阅读器),就可以反复使用打开的连接。
  • @Cylindric:实际上,dbConnection 确实在这里提到了这种行为:msdn.microsoft.com/en-us/library/…
  • @competent_tech:是的。我也那么认为。在上述情况下,调用Address.Populate 时,父方法仍将打开阅读器。重构当然可以解决这个问题,但是如果你完成了它,那么这个连接无论如何都可以回到池中。如果您建议这种方法,我觉得应该明确提及。
猜你喜欢
  • 1970-01-01
  • 2013-08-20
  • 1970-01-01
  • 2023-03-06
  • 2017-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多