首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >一段.NET“删规则调顺序”的代码,暴露了很多.NET人的真实水平:从能跑到扛住并发,我改了一下午

一段.NET“删规则调顺序”的代码,暴露了很多.NET人的真实水平:从能跑到扛住并发,我改了一下午

作者头像
云中小生
发布2026-05-20 15:41:42
发布2026-05-20 15:41:42
690
举报

做过后台开发的,基本都写过这类逻辑:

维护一个有序列表

什么审批规则排序、菜单顺序、流程步骤、数据字典顺序…… 需求几乎长一个样:

删除一项 → 后面的序号自动减1 → 序号永远连续不中断。

听起来简单吧? 但我最近正好在代码里翻到一段真实业务逻辑(脱敏过的), 看完我沉默了十分钟。

它不是不能跑, 但你要是把它扔到高并发或者数据量稍微大一点的场景里, 早晚出事

今天我就拿这段代码当案例, 从“能跑”一路改到“能上线扛流量”。 不吹架构,不炫技,全是真实落地过程。


一、先看原始代码(典型到想叹气)

代码语言:javascript
复制
public async Task RemoveOneAsync(Guid id, Guid? tenantId)
{
    ApprovalRuleInfo? rule = await _repository.FindOneAsync(id);
    if (rule is null)
        throw new ArgumentException("审批规则不存在");
    SearchConditionDTO searchCondition = new SearchConditionDTO(1, 9999, "OrderId", false);
    searchCondition.AddCondition("OrderType", ((int)rule.OrderType).ToString(), RelationEnum.Equal);
    ResultList rlist = await _repository.FindSomeAsync(searchCondition, tenantId, OperationTypeEnum.Write);
    foreach (var item in rlist.Rows)
    {
        if (item.ID == id)
        {
            item.Remove();
        }
        else
        {
            if (item.OrderId > rule.OrderId)
                item.UpdateOrder(item.OrderId - 1);
        }
    }
    _repository.UpdateBatch(rlist.Rows);
    return id;
}

你说它错了吧,也没有。 功能走得通,需求也满足。 但就是有一股“写完就跑”的味道

我当时列了一下问题:

  • 性能随数据量线性下降
  • 两个人同时删就乱套
  • 代码读起来绕脑子
  • 后面谁接手谁骂人

很多系统的线上卡顿、序号错乱、删除后数据对不上, 就是这种“能跑就行”的代码堆出来的。


二、先想清楚业务本质(这不是废话)

删除一个排序项并维护序号, 其实只有两件事:

  1. 删掉这条记录
  2. 同类型里,序号 > 被删序号的那些 → 序号全部减1

这是一个 区间更新,不是全量更新。 但原代码最大的问题就是: 把区间操作,硬写成了全量操作

你要是只有三五条数据还好, 一旦某个类型下有几百上千条, 它每次都把全部数据拉出来、遍历、再写回去—— 这是典型的“不知道数据库能批量操作”。

(这里本来有一张示意图,我画了个草稿,大家脑补一下:左边是查全表,右边只查OrderId大于某个值的)


三、第一刀:缩小数据范围(收益最直接)

原代码里这个条件,看得我血压有点高:

代码语言:javascript
复制
SearchConditionDTO(1, 9999, "OrderId", false);

9999?? 哪怕只有20条数据,它也敢查9999条回来。 而且是在内存里再判断 if (item.OrderId > rule.OrderId)

我就问一句: 为什么不在数据库里就过滤掉?

改成这样:

代码语言:javascript
复制
searchCondition.AddCondition("OrderId", rule.OrderId.ToString(), RelationEnum.GreaterThan);

就这么一行改动:

  • 查询数据量直接降 50%~99%
  • 内存里不需要再判断
  • 代码也干净了

这个优化,性价比最高。 不换架构、不改SQL(对,他们用的是自己封装的查询条件), 但收益肉眼可见。


四、第二刀:删除和更新别搅在一起

原代码的循环长这样:

代码语言:javascript
复制
foreach(...) {
    if(是删除项) 删除
    else 更新序号
}

看着好像也没啥, 但你要是出过一次bug就知道了: 到底是删错了,还是更新错了? 日志打出来都分不清。

我的习惯是 职责分离

代码语言:javascript
复制
// 1. 先删
await _repository.DeleteAsync(rule);
// 2. 再批量更新后面的序号
await _repository.UpdateBatchOrderAsync(rule.OrderType, rule.OrderId, tenantId);

一眼就知道在干什么。 这叫不叫“单一职责”我不在乎, 但下一个人接手的时候不会骂我

(这里本来有张对比图,我懒得画了,就是左边一堆if else,右边两行清晰代码)


五、第三刀:性能天花板(一条SQL搞定)

这是我最想说的。

很多人写业务代码,从来没想过可以不用循环

原代码: 查N条 → 遍历N条 → 更新N条 → 复杂度 O(N)

其实一条SQL就够了:

代码语言:javascript
复制
UPDATE ApprovalRuleInfo
SET OrderId = OrderId - 1
WHERE OrderType = @OrderType
  AND OrderId > @DeletedOrderId
  AND TenantId = @TenantId

就这么简单。 没有内存消耗,没有网络来回,数据库自己就搞定了。 不管你是10条还是10000条,时间基本恒定。

我第一次改成这种方式的时候, 同事问:“你确定不需要foreach?” 我直接让他跑了次性能对比,他不说话了。


六、第四刀:事务不是可有可无(血的教训)

原代码完全没有事务

这意味着什么? 高并发下,两个人同时删同类型的规则:

  • 序号可能重复
  • 可能出现断层
  • 甚至删了一半,程序崩了,后面的序号没更新

这些事情我线上都见过。 有一次半夜被叫起来,就是因为没加事务,数据乱成一锅粥。

正确姿势:

代码语言:javascript
复制
using var transaction = await _repository.BeginTransactionAsync();
try
{
    await _repository.DeleteAsync(rule);
    await _repository.UpdateBatchOrderAsync(rule.OrderType, rule.OrderId, tenantId);
    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}

这句话我希望你记住: 线上系统,事务不是“可选”,是“必须”。


七、顺手把代码规范补了(别留坑)

改都改了,干脆把那些小毛病一起修了:

  • 去掉那个 9999(太丑了)
  • 异常从 ArgumentException 改成明确的 EntityNotFoundException
  • 无意义的 return id 改成 Task
  • 所有查询强制带上租户ID(不然多租户直接崩)

这些不复杂,但能少挨很多骂


八、最终代码长这样(可以直接复制)

业务层

代码语言:javascript
复制
/// 
/// 删除审批规则,并自动维护后续序号/// 改了好几版,这个算能见人的/// 
public async Task RemoveOneAsync(Guid id, Guid? tenantId)
{
    var rule = await _repository.FindOneAsync(id, tenantId)
        ?? throw new EntityNotFoundException(nameof(ApprovalRuleInfo), id);
    using var transaction = await _repository.BeginTransactionAsync();
    try
    {
        await _repository.DeleteAsync(rule);
        await _repository.UpdateOrderAfterDeleteAsync(
            rule.OrderType,
            rule.OrderId,
            tenantId);
        await transaction.CommitAsync();
    }
    catch
    {
        await transaction.RollbackAsync();
        throw;
    }
}

仓储层(高性能SQL)

代码语言:javascript
复制
public async Task UpdateOrderAfterDeleteAsync(OrderTypeEnum orderType, int deletedOrderId, Guid? tenantId)
{
    var sql = @"
        UPDATE ApprovalRuleInfo
        SET OrderId = OrderId - 1
        WHERE OrderType = @OrderType
          AND OrderId > @DeletedOrderId
          AND TenantId = @TenantId";
    var param = new
    {
        OrderType = (int)orderType,
        DeletedOrderId = deletedOrderId,
        TenantId = tenantId
    };
    await ExecuteSqlAsync(sql, param);
}

异常类(有总比没有好)

代码语言:javascript
复制
public class EntityNotFoundException : Exception
{
    public EntityNotFoundException(string entityName, object key)
        : base($"[{entityName}] 主键 {key} 不存在") { }
}

九、一个有点“反常识”的思考

代码改完以后,我跟产品同学聊了一次。

我说: “序号真的必须连续吗?”

他愣了一下。 我们绝大多数场景里,OrderId 只是为了排序。 1,2,3,5,6 排序结果完全正确啊。

  • 如果只用于排序 → 删除时根本不需要更新序号
  • 如果UI要显示连续序号 → 前端临时算一下就行了
  • 只有少数场景真的必须连续 → 可以用定时任务统一整理

这个思路一出来, 删除操作直接从 O(N) 变成 O(1)。

这是我比较得意的地方: 最好的优化,有时候不是改代码,而是重新理解需求。


十、改完以后,差别在哪?

我简单列了个对比,不搞花哨表格:

方面

改之前

改之后

查询方式

全量拉回来

只查需要的区间

更新方式

内存里一条条改

一条SQL批量更新

性能

数据一多就慢

固定时间,几乎不变

并发安全

没事务,各种乱

事务保底,稳

代码可读性

得看好几遍

一眼就懂

内存占用

极低

谁愿意维护

没人想碰

随便谁都能接


十一、一点真心话

这段代码看起来很小, 但把它改好的过程,其实能看出一个人在哪一层思考:

  1. 数据范围:只拿需要的数据,别贪心
  2. 执行方式:能用批量就别循环
  3. 安全一致性:事务、锁、并发控制,不能偷懒
  4. 业务本质:能不能不做?能不能更简单?

我越来越觉得, 高级工程师不是写得出一堆复杂代码, 而是能把复杂的东西变得简单、安全、快。

(这里本来有张四层图,我懒得配了,你们脑补一下金字塔就行)


最后

这段“删规则调顺序”的代码, 看起来只是日常业务中再普通不过的一段。 但真把它改好、改稳、改到能扛并发, 它就能区分: 能跑 vs 能上线

优化的从来不是一行代码, 而是系统的稳定性、并发能力、以及后面人的心情。

优化没有尽头,但思路有迹可循。


👋 我是 [云中小生],一个经常在后端踩坑又爬出来的程序员。 不定期分享:后端实战、性能优化、真实踩坑、架构思考。 不整虚的,只写能落地的。

(点击关注,修炼不迷路👇

▌转载请注明出处,渡人渡己

🌟 感谢道友结缘! 若本文助您突破修为瓶颈,不妨【转发功德】,让更多道友共参.NET天道玄机。修真之路漫漫,我们以代码为符,共绘仙途!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 .NET修仙日记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、先看原始代码(典型到想叹气)
  • 二、先想清楚业务本质(这不是废话)
  • 三、第一刀:缩小数据范围(收益最直接)
  • 四、第二刀:删除和更新别搅在一起
  • 五、第三刀:性能天花板(一条SQL搞定)
  • 六、第四刀:事务不是可有可无(血的教训)
  • 七、顺手把代码规范补了(别留坑)
  • 八、最终代码长这样(可以直接复制)
    • 业务层
    • 仓储层(高性能SQL)
    • 异常类(有总比没有好)
  • 九、一个有点“反常识”的思考
  • 十、改完以后,差别在哪?
  • 十一、一点真心话
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档