Kra*_*ica 30 .net c# sql-server
我有一个C#应用程序,它从一个有点不稳定的环境中托管的SQL Server中获取数据.我无法解决环境问题,所以我需要尽可能优雅地处理它们.
为此,我想重新尝试基础设施故障导致的操作,例如网络故障,SQL服务器脱机,因为它们正在重新启动,查询超时等.同时,我不想要如果逻辑错误失败,则重试查询.我只是希望那些将异常冒泡到客户端.
我的问题是:区分环境问题(丢失连接,超时)和其他类型的异常(即使环境稳定会发生逻辑错误之类的事情)的最佳方法是什么.
C#中有一个常用的模式来处理这样的事情吗?例如,是否有一个属性我可以在SqlConnection对象上检查以检测失败的连接?如果没有,解决这个问题的最佳方法是什么?
对于它的价值,我的代码并不特别:
using (SqlConnection connection = new SqlConnection(myConnectionString))
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = mySelectCommand;
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
// Do something with the returned data.
}
}
}
Run Code Online (Sandbox Code Playgroud)
Adr*_*tti 51
一个SqlException(可能)包装多个SQL Server错误.您可以使用Errors属性迭代它们.每个错误是SqlError:
foreach (SqlError error in exception.Errors)
Run Code Online (Sandbox Code Playgroud)
每个SqlError都有一个Class属性,您可以用来粗略地确定您是否可以重试(如果您还需要重新创建连接,则重试).来自MSDN:
Class <10表示您传递的信息中的错误(可能),如果您没有更正输入,则无法重试.Class从11到16是"由用户生成"然后,如果用户首先不纠正他的输入,则可能再次无法做任何事情.请注意,第16类包含许多临时错误,第13 类包含死锁(感谢EvZ),因此如果您逐个处理它们,可以排除这些类.Class从17到24是一般硬件/软件错误,您可以重试.当Class为20或更高时,您还必须重新建立连接.22和23可能是严重的硬件/软件错误,24表示媒体错误(应该警告用户,但如果它只是一个"临时"错误,您可能会重试).您可以在此处找到每个班级的更详细说明.
通常,如果您使用类处理错误,则无需确切地知道每个错误(使用error.Number属性或exception.Number只是SqlError该列表中第一个的快捷方式).这样做的缺点是,当它无用时(或无法恢复错误),您可以重试.我建议采用两步法:
SELECT * FROM master.sys.messages)以查看您要处理的内容(知道如何).该视图包含所有受支持语言的消息,因此您可能需要按msglangid列过滤它们(例如,英语为1033).Class为13或高于16(并重新连接,如果20或更高).关于高年级的一个词.如何处理这些错误并不简单,这取决于许多因素(包括应用程序的风险管理).作为简单的第一步,我在尝试写操作时不会重试22,23和24:如果数据库,文件系统或介质严重受损,那么写入新数据可能会使数据完整性恶化(SQL Server非常小心)即使在严峻的情况下,也不要为了查询而破坏数据库).损坏的服务器,取决于您的数据库网络架构,甚至可能是热交换(自动,在指定的时间后,或触发指定的触发器).始终咨询并在您的DBA附近工作.
重试策略取决于您正在处理的错误:免费资源,等待待处理操作完成,采取替代操作等.一般情况下,只有在所有错误都是"可重试"时才应重试:
bool rebuildConnection = true; // First try connection must be open
for (int i=0; i < MaximumNumberOfRetries; ++i) {
try {
// (Re)Create connection to SQL Server
if (rebuildConnection) {
if (connection != null)
connection.Dispose();
// Create connection and open it...
}
// Perform your task
// No exceptions, task has been completed
break;
}
catch (SqlException e) {
if (e.Errors.Cast<SqlError>().All(x => CanRetry(x))) {
// What to do? Handle that here, also checking Number property.
// For Class < 20 you may simply Thread.Sleep(DelayOnError);
rebuildConnection = e.Errors
.Cast<SqlError>()
.Any(x => x.Class >= 20);
continue;
}
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
裹在一切try/ finally妥善处置连接.有了这个简单的假天真CanRetry()功能:
private static readonly int[] RetriableClasses = { 13, 16, 17, 18, 19, 20, 21, 22, 24 };
private static bool CanRetry(SqlError error) {
// Use this switch if you want to handle only well-known errors,
// remove it if you want to always retry. A "blacklist" approach may
// also work: return false when you're sure you can't recover from one
// error and rely on Class for anything else.
switch (error.Number) {
// Handle well-known error codes,
}
// Handle unknown errors with severity 21 or less. 22 or more
// indicates a serious error that need to be manually fixed.
// 24 indicates media errors. They're serious errors (that should
// be also notified) but we may retry...
return RetriableClasses.Contains(error.Class); // LINQ...
}
Run Code Online (Sandbox Code Playgroud)
一些相当棘手的方式找到非关键错误的列表在这里.
通常我将所有这些(样板文件)代码嵌入到一个方法中(我可以隐藏所有用于创建/配置/重新创建连接的脏东西):
public static void Try(
Func<SqlConnection> connectionFactory,
Action<SqlCommand> performer);
Run Code Online (Sandbox Code Playgroud)
要像这样使用:
Try(
() => new SqlConnection(connectionString),
cmd => {
cmd.CommandText = "SELECT * FROM master.sys.messages";
using (var reader = cmd.ExecuteReader()) {
// Do stuff
}
});
Run Code Online (Sandbox Code Playgroud)
请注意,当您不使用SQL Server时,也可以使用骨架(出错时重试)(实际上它可以用于许多其他操作,如I/O和网络相关的东西,所以我建议编写一般功能并广泛地重复使用它.
我不知道任何标准,但这里列出了Sql-Server我通常认为可以重试的例外情况,并且还带有 DTC 调味:
catch (SqlException sqlEx)
{
canRetry = ((sqlEx.Number == 1205) // 1205 = Deadlock
|| (sqlEx.Number == -2) // -2 = TimeOut
|| (sqlEx.Number == 3989) // 3989 = New request is not allowed to start because it should come with valid transaction descriptor
|| (sqlEx.Number == 3965) // 3965 = The PROMOTE TRANSACTION request failed because there is no local transaction active.
|| (sqlEx.Number == 3919) // 3919 Cannot enlist in the transaction because the transaction has already been committed or rolled back
|| (sqlEx.Number == 3903)); // The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.
}
Run Code Online (Sandbox Code Playgroud)
关于重试,建议在重试之间添加随机延迟,以减少例如相同的 2 个事务再次死锁的可能性。
对于某些DTC相关错误,可能需要删除连接(或者最坏的情况是 SqlClient.SqlConnection.ClearAllPools()) - 否则无效连接将返回到池中。
小智 5
您可以简单地将SqlConnectionStringBuilder属性重命名为sql连接.
var conBuilder = new SqlConnectionStringBuilder(Configuration["Database:Connection"]);
conBuilder.ConnectTimeout = 90;
conBuilder.ConnectRetryInterval = 15;
conBuilder.ConnectRetryCount = 6;
注意: - 必需.Net 4.5或更高版本.