🌹欢迎来到《小5讲堂》🌹
🌹这是《C#》系列文章,每篇文章将以博主理解的角度展开讲解。🌹
🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹
目录
前言
最近博主在开发一个项目时,用到的都是基本框架功能,有些造轮子的意思。
如果不留意,那么很容易犯一些代码逻辑上的错误。
自己封装框架和使用现有成熟框架最大区别就是容易踩坑,除非你有比较多的框架开发经验。
锁定分析
Lock锁对象
这里博主犯了个小细节错误,锁的对象和范围逻辑不对,导致重复数据出现,大事务里存在同表查询和添加,即使在小量并发下,出现争抢资源,导致锁。
实例化锁对象
下面是错误写法,有发现那里不对吗?
namespace MvcMyNameSpace
{
public class GetClass
{
private object lockObj = new object();
public string GetNo(string tableName, string prefix)
{
lock (lockObj)
{
// 查询表
string sql = "select top 1 * from Test"
}
}
}
静态实例化
正确写法,确保锁对象是当前唯一
namespace MvcMyNameSpace
{
public class GetClass
{
private static object lockObj = new object();
public string GetNo(string tableName, string prefix)
{
lock (lockObj)
{
// 查询表
string sql = "select top 1 * from Test"
}
}
}
锁范围
博主在上面独立方法代码块加上锁后,运行一段时间任务后,发现还是会出现重复索引。
后来排查发现,是并发会在最外层接口方法没有锁定。
之后在接口API整个方法加了锁后,就再也没有出现了重复索引。
事务锁
博主这里是在一个大事务内进行了表查询,在高并发下就会出现,多个事务可能同时尝试更新或读取相同的数据行或表,导致资源竞争。
重复键
对于sql server数据库而言,可对表的字段设置唯一索引,确保数据库表唯一性。
如果表设置了唯一索引,那么还会出现重复,说明程序端代码逻辑不对,写法有问题。
博主此处出现的错误就是在并发情况,至少有两个并发同一时间获取到同一条记录。
DeepSeek解决方案
从你提供的代码和错误信息来看,程序在执行 SQL 命令时遇到了死锁问题。死锁通常发生在多个事务同时竞争相同的资源,并且每个事务都在等待其他事务释放锁时发生。SQL Server 检测到死锁后,会选择其中一个事务作为“牺牲品”并终止它,以便其他事务可以继续执行。
死锁的原因
在你的代码中,死锁可能由以下原因引起:
- 并发事务竞争相同的资源:多个事务可能同时尝试更新或读取相同的数据行或表,导致资源竞争。
- 事务范围过大:如果事务涉及多个表或大量数据,死锁的风险会增加。
- 锁的顺序不一致:如果不同的事务以不同的顺序锁定资源,可能会导致死锁。
解决方案
以下是几种可能的解决方案:
1. 减少事务范围
- 尽量减少事务的持续时间,确保事务只包含必要的操作。
- 如果可能,将事务拆分为多个较小的事务,减少锁的持有时间。
2. 优化 SQL 语句
- 确保 SQL 语句尽可能高效,减少锁的持有时间。
- 使用
WITH (NOLOCK)
提示来避免读取操作时的锁竞争(但要注意脏读的风险)。 - 确保更新操作只锁定必要的行,而不是整个表。
3. 使用锁提示
- 在 SQL 语句中使用锁提示来控制锁的行为。例如,使用
UPDLOCK
或ROWLOCK
来减少锁的粒度。 - 例如:
SELECT * FROM TableName WITH (UPDLOCK) WHERE ...
4. 重试机制
- 在捕获到死锁异常后,实现重试机制。死锁通常是暂时的,重试后事务可能会成功执行。
- 例如:
int retryCount = 3; while (retryCount > 0) { try { // 执行 SQL 命令 cmd.ExecuteNonQuery(); break; } catch (SqlException ex) { if (ex.Number == 1205) // 1205 是死锁错误代码 { retryCount--; if (retryCount == 0) throw; // 重试次数用尽,抛出异常 Thread.Sleep(1000); // 等待一段时间后重试 } else { throw; // 其他异常直接抛出 } } }
5. 检查索引
- 确保表上有适当的索引,以减少锁的竞争。缺少索引可能导致全表扫描,增加锁的冲突。
6. 分析死锁图
- 使用 SQL Server 的死锁图(Deadlock Graph)来分析死锁的原因。你可以通过 SQL Server Profiler 或 Extended Events 来捕获死锁图,了解哪些资源和事务导致了死锁。
示例代码改进
以下是一个简单的重试机制示例,你可以在代码中添加:
int retryCount = 3;
while (retryCount > 0)
{
try
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = connection;
foreach (String str in List)
{
sqlText = "";
// 添加参数
ArrayList paramsArray = listParams[i];
foreach (Dictionary<string, object> paramsItem in paramsArray)
{
foreach (var keyItem in paramsItem)
{
if (string.IsNullOrEmpty($"{keyItem.Value}"))
{
cmd.Parameters.AddWithValue(keyItem.Key, DBNull.Value);
}
else
{
cmd.Parameters.AddWithValue(keyItem.Key, $"{keyItem.Value}");
}
if (str.Contains("update"))
{
sqlText += $"{keyItem.Key.Replace("@", "")}='{keyItem.Value}',";
}
else
{
sqlText += $"'{keyItem.Value}',";
}
}
}
allSql = $"{str}";
// 在这里执行你的 SQL 命令
cmd.CommandText = str;
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
i++;
}
rows += 1;
}
}
break; // 执行成功,退出循环
}
catch (SqlException ex)
{
if (ex.Number == 1205) // 1205 是死锁错误代码
{
retryCount--;
if (retryCount == 0)
throw; // 重试次数用尽,抛出异常
Thread.Sleep(1000); // 等待一段时间后重试
}
else
{
throw; // 其他异常直接抛出
}
}
}
总结
死锁是数据库系统中常见的问题,通常通过优化事务范围、SQL 语句、锁策略以及实现重试机制来解决。通过分析死锁图和调整代码逻辑,可以有效减少死锁的发生。
相关文章
【C#】事务(进程 ID 64)与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品。请重新运行该事务。不能在具有唯一索引“XXX_Index”的对象“dbo.Test”中插入重复键的行。
【C#】使用DeepSeek帮助评估数据库性能问题,C# 使用定时任务,每隔一分钟移除一次表,再重新创建表,和往新创建的表追加5万多条记录
【C#】合理使用DeepSeek相关AI应用为我们提供强有力的开发工具,在.net core 6.0框架下使用JsonNode动态解析json字符串,如何正确使用单问号和双问号做好空值处理
【C#】已经实体类和动态实体类的反射使用方法,两分钟回顾,码上就懂
【C#】使用vue3的axios发起get和post请求.net framework部署的API显示跨域
【C#】.net core 6.0 webapi 使用core版本的NPOI的Excel读取数据以及保存数据
【C#】pdf按页分割文件,以及分页合并,效果还不错,你值得拥有
【C#】未能加载文件或程序集“CefSharp.Core.Runtime.dll”或它的某一个依赖项。找不到指定的模块。
【C#】.net core 6.0 在program时间格式统一json格式化,并列举program默认写法和简化写法
【C#】.net core 6.0 ApiController,API控制器方法,API接口以实体类作为接收参数应该注意的点
【C#】 SortedDictionary,查找字典中是否存在给定的关键字
【C#】.net core 6.0 MVC返回JsonResult显示API接口返回值不可被JSON反序列化
【C#】.net core 6.0 使用第三方日志插件Log4net,配置文件详细说明