处理数据库连接问题的正确方法

14

尝试连接数据库时出现以下错误:

在建立与 SQL Server 的连接时发生了一些与网络或实例特定的错误。找不到服务器或无法访问。请验证实例名称是否正确并且 SQL Server 已配置为允许远程连接。(提供程序:Named Pipes Provider,错误:40 - 无法打开到 SQL Server 的连接)

有时我会遇到这个错误,有时则不会。例如:当我第一次运行程序时,它会成功地打开连接;但当我第二次运行程序时,就会出现此错误。接下来,如果我再次运行程序,那么错误就不会再出现。

当我尝试通过 SSMS 连接同一数据库服务器时,连接是成功的,但是只有在我的程序中才会遇到这个网络问题。

数据库不在我的LOCAL上。

我在使用本地数据库时没有遇到这个错误。

代码:

public class AddOperation
{
    public void Start()
    {
          using (var processor = new MyProcessor())
          {
              for (int i = 0; i < 2; i++)
              {
                  if(i==0)
                  {
                     var connection = new SqlConnection("Connection string 1");
                     processor.Process(connection);
                  }
                  else
                  {
                      var connection = new SqlConnection("Connection string 2");
                      processor.Process(connection);
                  }   
              }
          }
    }       
}

public class MyProcessor : IDisposable
{
    public void Process(DbConnection cn)
        {
            using (var cmd = cn.CreateCommand())
            {
                cmd.CommandText = "query";
                cmd.CommandTimeout = 1800;
                cn.Open();//Sometimes work sometimes dont
                using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
                { 
                   //code
                }
            }
        }
}

所以我对两件事感到困惑:

1)连接超时:我是否应该增加连接超时时间,这样会解决我的异常连接问题吗?

2)重试尝试策略:我是否应该像下面这样实现重试连接机制:

public static void OpenConnection(DbConnection cn, int maxAttempts = 1)
        {
            int attempts = 0;
            while (true)
            {
                try
                {
                    cn.Open();
                    return;
                }
                catch
                {
                    attempts++;
                    if (attempts >= maxAttempts) throw;
                }
            }
        }

我对这两个选项感到困惑。

能否有人建议我应该用什么更好的方式来处理这个问题?


那么,SQL Server是否配置为允许远程连接? - DrHouseofSQL
1
当你说数据库在Azure上时,是指虚拟机(IaaS)还是Azure SQL数据库(PaaS)?SSMS是否在与应用程序相同的机器上?如果是不同的机器,请使用FQDN测试端口连接性:powershell -Command echo ((new-object Net.Sockets.TcpClient).Client.Connect('yoursqlserver.database.windows.net', 1433)) 'success' - Dan Guzman
@DrHouseofSQL 但如果它是被允许的,那么为什么有时我能够连接呢? - ILoveStackoverflow
@DanGuzman 我的数据库在虚拟机上,我的应用程序是本地的而不在 Azure 中,因此我的本地应用程序正在尝试连接位于 Azure VM 上的数据库。 - ILoveStackoverflow
提供与此问题相关的所有信息以及我尝试过的内容后,仍然收到了负评。我想我不得不再发一个问题,询问如何在提供一切信息后停止负评 :) - ILoveStackoverflow
7个回答

8

所有与远程服务通信的应用程序都对瞬态故障非常敏感。

如其他答案所述,如果您的客户端程序使用.NET Framework类System.Data.SqlClient.SqlConnection连接到SQL数据库,请使用.NET 4.6.1或更高版本(或.NET Core)以便使用其连接重试功能。

在为SqlConnection对象构建连接字符串时,请协调以下参数的值:

ConnectRetryCount:默认值为1。范围是0到255。

ConnectRetryInterval:默认值为1秒。范围是1到60。

Connection Timeout:默认值为15秒。范围是0到2147483647。

具体而言,您选择的值应使以下等式成立:

Connection Timeout = ConnectRetryCount * ConnectionRetryInterval

现在,让我们来谈谈第二个选项,当您的应用程序具有自定义重试逻辑时,它将增加总重试次数 - 对于每个自定义重试,它将尝试ConnectRetryCount次。例如,如果ConnectRetryCount = 3且自定义重试= 5,则将尝试15次。您可能不需要那么多次重试。
如果您只考虑自定义重试与连接超时:
连接超时通常是由于丢包率更高的网络(例如,蜂窝网络或弱WiFi)或高网络流量引起的。选择最佳使用策略取决于您。
以下准则有助于解决瞬态错误:
  1. https://learn.microsoft.com/en-us/azure/sql-database/sql-database-connectivity-issues

  2. https://learn.microsoft.com/en-in/azure/architecture/best-practices/transient-faults


8

1
但是我的问题是 ConnectionTimeout 属性能帮我解决这个奇怪的网络问题吗? - ILoveStackoverflow
你应该充分利用所有三个参数,其中包括超时和重试。 - ErikEJ
谢谢,但我不明白为什么有时会出现网络错误,而有时却没有。你能否告诉我一些相关信息?我想了解其中的原因。 - ILoveStackoverflow

5
作为您可以在这里阅读到的,即使在Azure VM(IaaS)上安装了SQL Server,也建议进行重试逻辑。

故障处理:您的应用程序代码包括重试逻辑和瞬态故障处理吗?在代码中包含适当的重试逻辑和瞬态故障处理是一种通用最佳实践,无论是在本地还是在云中,无论是IaaS还是PaaS。如果缺少此特性,则可能会在Azure SQLDB和Azure VM中的SQL Server上引发应用程序问题,但在此场景中,推荐使用后者。

建议使用增量重试逻辑。
有两种基本方法来实例化应用程序块所需的对象。在第一种方法中,您可以在代码中明确地实例化所有对象,如下面的代码片段所示:
var retryStrategy = new Incremental(5, TimeSpan.FromSeconds(1), 
  TimeSpan.FromSeconds(2));

var retryPolicy =
  new RetryPolicy<SqlDatabaseTransientErrorDetectionStrategy>(retryStrategy);

在第二种方法中,您可以像以下代码片段所示从配置数据中实例化和配置对象:
// Load policies from the configuration file.
// SystemConfigurationSource is defined in 
// Microsoft.Practices.EnterpriseLibrary.Common.
using (var config = new SystemConfigurationSource())
{
  var settings = RetryPolicyConfigurationSettings.GetRetryPolicySettings(config);

  // Initialize the RetryPolicyFactory with a RetryManager built from the 
  // settings in the configuration file.
  RetryPolicyFactory.SetRetryManager(settings.BuildRetryManager());

  var retryPolicy = RetryPolicyFactory.GetRetryPolicy
  <SqlDatabaseTransientErrorDetectionStrategy>("Incremental Retry Strategy");   
   ... 
   // Use the policy to handle the retries of an operation.

}

欲了解更多信息,请访问此文档


为什么 ConnectionTimeout 属性对我没有帮助? - ILoveStackoverflow
超时可能具有非常独特的性质,可能是由于不良模式设计、缺乏索引、次优查询计划、阻塞、超时设置错误配置等原因导致的。如果超时是您的情况,我建议您采用不同的方法。 - Alberto Morillo
所以从上述的两种策略中,我应该选择哪一个? - ILoveStackoverflow
@AlbertoMorillo,你听说过Polly吗?它是一个.NET弹性和瞬态故障处理库,可以用于解决这种问题。https://github.com/App-vNext/Polly - Bryan
这是我已经使用了一段时间。WaitAndRetry 对于该问题是适用的,它给数据库恢复的机会。我在这里的一个答案中发布了一个示例。 - Bryan

2
考虑使用Polly
您可以使用类似以下的简单代码 -
RetryPolicy retryPolicy = Policy.Handle<Exception>()
            .WaitAndRetry(3, retryAttempt => 
TimeSpan.FromSeconds(retryAttempt));

var result = retryPolicy.Execute(() => someClass.DoSomething());

这将重试请求最多三次。

1

你是使用SQL Express或Workgroup Edition吗?如果是,服务器可能太忙而无法响应。

为了排除网络问题,在命令提示符中执行PING -t SqlServername。是否每个ping都成功返回,还是有一些丢失?这可能是网络中断的指示器,也可能导致此错误,例如故障交换机。如果它们全部丢失,则(考虑到您的数据库连接有时有效),很可能是由于某个防火墙阻止了ping:如果您找到该块并暂时解除阻止,这可能有助于诊断。

错误消息表明您正在使用命名管道。您是有意使用命名管道吗?对于大多数场景(包括Azure数据库),我建议在SQL Server Configuration Manager中启用TCP/IP并禁用命名管道。

根据您的Azure数据库所在的距离远近,由于路由器和防火墙的延迟有时会影响Kerberos和/或相关时间。您可以通过在连接字符串中使用端口来避免回传到端口1434进行实例枚举。我假设您已经使用FQDN。例如: server\instance,port


@用户:“我的连接字符串是这样的”,然后未能演示端口的使用失败。您能回答一下有关SQL版本、从应用服务器到数据库服务器的ping和命名管道的问题吗? - Richardissimo
@用户:仍在等待这些问题的答案,并且希望得到一些承认,即上面的评论没有展示出对端口的使用。 - Richardissimo
@用户,好的,ping命令可能被防火墙屏蔽了,所以除非您可以打开防火墙以进行ping(这有助于诊断),否则不用管它。我还在等待您的SQL服务器版本,是否有意使用命名管道,并确认您上面的评论没有像我建议的那样使用端口。 - Richardissimo
但是在网络错误问题中,命名管道的作用是什么? - ILoveStackoverflow
是的,但我看到的大多数与此错误相关的解决方案从未提到过NamedPipe。因此我想问一下 :) - ILoveStackoverflow
显示剩余6条评论

1

完全有可能连接会中断。"分布式计算的谬论" :). 可能是网络连接问题。可能在任何一端。

我建议:(假设 Azure 上的机器启用了防火墙)

  1. ping 服务器并查看是否有丢失。

ping (server).database.windows.net

  1. tracert
  2. telnet 也可能是你的朋友。

以上三个步骤应该能帮助你找到问题所在。

我认为你的重试逻辑很好。

关于你的问题

增加超时时间 只有在你确定查询需要很长时间的情况下才需要增加超时时间。如果对于一个简单的插入操作,你必须增加超时时间,那么问题可能是网络连接。

重试逻辑 如已发布,它现在是框架的一部分,你可以利用它或者你创建的那个应该也不错。理想情况下,即使你确定连接和速度,也最好有重试逻辑。以防万一 :)。


1
您应该增加超时时间,因为连接到SQL服务器的时间有许多步骤,因此在第一次建立连接时需要一些时间。在连接建立后,连接会被缓存在内存中以便在后续查询中重复使用。
请参考下面的链接以更详细地了解连接池: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-connection-pooling 正如您所提到的,这个错误有时会出现,而不总是出现,因此可能存在一些网络和连接性因素。 SQL连接的默认超时时间为15秒。我认为如果将其改为30秒,它应该可以工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接