《故障复盘 · 记一次事务用法错误导致的大量锁表问题》

news2025/7/15 13:20:15

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

文章目录

    • 写在前面的话
    • 故障描述
    • 排查过程
      • 排查前期
      • 排查过程 1
      • 排查过程 2
      • 排查过程 3
      • 排查结论
    • 知识拓展
      • 事务前置知识
      • 回到上述问题
      • 关于手动控制
      • 关于事务封装
      • 问题排查经验
      • 事务使用经验
    • 总结陈词


写在前面的话

博主所在公司的产品线,部署上线了多家客户,遇到的线上故障的场景也较多,这里新开一个故障复盘系列,记录并分享一下这些故障的的定位、分析、解决过程。
首先分享的这篇,是排查定位耗时较久的一个由于事务用法错误,导致的大量锁表,进而引发大范围故障的问题。


故障描述

某客户线上环境的死锁问题持续了一个多月了,隔一天会出现一次,业务侧的代码,陆续调整过几次,但都治标不治本。
现象大概是,业务高峰期(9点-9点30),会出现大量锁表,DBA介入杀锁有时候仍无法正常,需要搭配重启相关服务才可以。
相关报错截图如下所示:
image.pngimage.png
由于影响越来越大,公司领导组织研发骨干,针对DBA提供的数据库锁表语法.xslt,以此为突破口进行技术攻关,最终发现了相关问题代码,修正后故障得到恢复,这里针对本次故障进行复盘总结,交流分享排查经验和事务用法。


排查过程

排查前期

【DBA发出锁表Excel】
2024-03-20下午,DBA发出了锁表语法,如下图所示,发现单个事务就执行了大量的语法,9点10分开始事务,9点25失败回滚,持续了15分钟的事务(该事务包含了 976 个语法)。
image.png

【分析锁表业务语法特征】
通过分析上面的数据库锁表语法发现里面涉及到的语法并不是来源于同一个接口,且业务范围也各不相同,说明各个不同业务的数据库操作都被嵌入到了同一个事务当中,且定位到锁表语法来自于收费服务,后续开始重点排查收费服务项目。

【组织人员排查】
Excel 暴露出一些问题:

  • 这些语法背后的数据确实没入库,但为何用户没有反馈问题
  • 语法涉及的接口看日志是操作成功了,但是为何数据没入库
  • 为什么这么多语句会在同一个事务中一起 rollback

疑问点比较多,但可以先从“为什么接口成功了,但数据确没有入库”这一点来分析。


排查过程 1

通过Excel提供的信息,结合应用日志分析,定位到收费服务的encounterCardInfoAdd是属于有问题的接口。
从应用日志看,该接口基本都是INFO,即操作成功的。
接口完整地址:[HTTP] /open/thirdParty/physical/encounterCardInfoAdd
但是比对了SkyWalking,确实DBA反馈的Excel里面合并到一个事务的SQL,所在的接口在SkyWaling看就是没有 commit。
image.png
然后该接口的正常的情况,看日志是有commit的,如下图,89ba3310ce46ffa2 没提交,ef65b1e6ea8a2280有提交
image.png
查看代码,除了一些规范问题,并没有看到什么不妥之处,也没有手动控制事务。
但没有定位到,为什么会有这种接口成功,但是没有commit或rollback的情况。


排查过程 2

上面接口上没看出来问题,那就继续观察现场环境,发现下面两个异常点。
1、锁的高峰期,收费控制台日志有大量异常:
Closed Connection org.springframework.dao.RecoverableDataAccessException: ### Error querying database. Cause: java.sql.SQLRecoverableException: Closed Connection
30分钟2000个,其中9点28分,1分钟有500个。
其他服务和其他时间段都没有。
image[1].png
2、锁的高峰期,从prometheus看,收费的数据库连接数比其他服务高很多,同时段其他服务,或非该时段,收费的连接数正常。
image[1].png
在这里插入图片描述

从上面两个点,可以得出的结论是,出现锁问题的时间段,确实收费服务的数据库连接是有问题的。
结合之前排查来看,感觉大概率和手动开启事务但没操作回滚提交有关系,应该和TransactionSynchronizationManager.registerSynchronization关系不大。
继续测试了一下,只要有手动开启事务,但没有提交。
这次事务里面又有for update语句,那数据就会一直处于锁状态,这时候其他也锁同样数据的接口,将被阻塞。
如果请求量又比较大,就会出现杀完锁,又马上出现锁的情况。
之前现场有反馈,每次出现问题,必须重启收费,才可以正常,应该也是重启后才真正释放了链接。
这种手动开启事务未提交,比较贴合昨天Skywaling没提交但是接口没报错,以及现场的日志耗时情况。
至于小孙发的Excel,目前还不好判断。怀疑是程序重启或者触发了回收机制,一次性rollbabck了,但目前貌似druid没配置回收。


排查过程 3

继续通过Excel,结合应用日志分析,收费服务的savePrescriptionToHospital方法也可能存在问题。
接口完整地址:[HTTP] /open/internetHospital/savePrescriptionToHospital
接口链路ID:ce07a0a7f900548a
接口触发时间:2024-03-20 09:09:52.292
接口现象如下:
1、该接口发生在问题产生的时间段,接口显示成功,但是可以看到相关语法在上面的 Excel 却显示了回滚;
2、查看SkyWaling,也看到了前面类似的情况(有的有commit、有的没有);
3、查看该接口的关联日志,该接口请求了医嘱的saveSelectedRecords,该接口存在异常;
通过查看代码,发现了问题,如下图所示:该接口的子方法中,手动开启了事务,但在调用医嘱接口,报错的时候,没有做任何处理,导致是否没有提交或回滚。
企业微信截图_17109850224097.png
上面的代码并未使用 try-catch 来保证事务开启后一定会被提交或者回滚,结合该接口对应的链路追踪信息,可以发现在调用 this.saveSelectedRecords 时抛出了异常。于是该事务一直处于开启状态,后续复用该连接的请求,对数据库进行操作时会默认被该事务管理。
image.png

【另一种思路】
当没有发现问题接口的情况下,出现上述没有commit的情况。
另外一个思路就是,如果正常通过框架提供的 @Transactional 来进行数据库事务操作,是不可能出现不同接口的数据库操作被关联到同一个事务的情况,所以上述的情况只能是开发者使用了 Spring 事务管理器DataSourceTransactionManager来手动管理各个事务阶段提交的场景,且开启后未正确处理相关异常等逻辑,没有及时关闭,导致数据库事务一致处于开启状态。


排查结论

故障的原因是因为收费服务下的某个接口,没有正确使用Spring事务管理器,手动开启事务后,当程序发生异常,没有做出相应的处理,导致事务一直处于开启状态。
同时,这些开启的事务被绑定到了 Tomcat 的请求连接池中,当其他请求进来复用了这些请求连接时,会自动持有之前未关闭的事务,这导致不同的业务接口对数据库的操作一直处于同一个事务中,没有被正确提交。


知识拓展

Tips:问题是基本排查出来也解决了,但是没有知识总结,那就是不是一个完整的技术复盘。
Tips:很明显,这次故障是由于程序猿对事务用法不熟悉、或者说不严谨导致的了,因此针对事务相关知识补充。

事务前置知识

Spring 支持两种事务方式,分别是声明式事务和编程式事务,两者各有优缺点。
声明式事务,其代表就是使用@Transactional,优点是简化配置、降低代码侵入性、易于理解。
缺点主要有:

  • 不够灵活,仅支持方法级别的事务控制,无法实现细粒度的事务控制;
  • 有较多导致注解失效的场景需要考虑,例如自调用问题、非public、异常不匹配、被标记回滚等;

编程式事务,典型代表有手动控制DataSourceTransactionManager直接使用TransactionTemplate
优点和@Transactional是相对的,灵活性高,细粒度的控制。
缺点主要有:

  • 代码冗长,编程式事务管理需要在业务代码中显式地编写事务管理逻辑,可能会导致代码变得冗长和复杂;
  • 可读性差,由于事务管理逻辑与业务逻辑交织在一起,可能会降低代码的可读性和可维护性;
  • 由于是手动控制,容易产生未关闭事务的情况出现;

综上所述,@Transactional 注解和编程式事务管理各有优缺点,可以根据项目的需求、复杂度和团队的技术水平来选择合适的事务管理方式。在简单的业务场景中,@Transactional 注解可能更加适用,而在复杂的事务管理需求下,编程式事务管理可能更加灵活。


回到上述问题

前面介绍完声明式事务和编程式事务的优缺点,公司框架鉴于安全性考虑,大部分简单场景推荐直接使用Spring声明式事务即@Transactional注解完成,但由于某产品线业务场景较为复杂,需要借助编程式事务写法,更精细的手动控制事务的提交和回滚动作。
全局搜该产品线的项目代码,可以看到如下图的大量事务控制代码。
image.png
不管是这次的遇到的锁问题,还是事务错乱的问题。追其根源,还是研发人员对手动控制事务的把控不到位,事务开启后如果没正常关闭,会导致后续各种问题的出现。
收费服务的接口手动开启了事务,期间Feign调用了医嘱服务,但没有对这部分代码块进行异常捕获,当医嘱服务异常,此方法直接结束,事务未关闭,一直处于活动状态,本事务里面涉及的锁表语法都会阻塞,同时造成后续其他影响。
企业微信截图_17109850224097.png


关于手动控制

1、尽量使用try-catch代码块控制手动事务代码,保障异常发生时,可以正常回滚事务;
2、当又遇到if-else分支时,要充分考虑每个代码段都有提交或回滚动作,不要有疏漏;
3、如果怕疏漏,可以在finally添加兜底操作,或者封装一个公用事务操作方法;
4、可以尝试使用Spring自带的TransactionTemplate来尝试操作事务;
5、研发主管审核手动控制事务的代码要特别注意,减少错误几率;

【某编程事务示例代码】

public class TransactionService {

    @Autowired
    private DataSourceTransactionManager transactionManager;

    public void someTransactionalMethod() {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 设置事务隔离级别,默认是 ISOLATION_DEFAULT
        def.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_DEFAULT);
        // 设置事务传播行为,默认是 PROPAGATION_REQUIRED
        def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);

        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 执行事务操作,例如执行数据库操作
            // ...

            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 如果发生异常,则回滚事务
            transactionManager.rollback(status);
            throw e; // 可以选择抛出异常或者处理异常
        } finally {
            // 确保在方法结束时关闭事务
            if (!status.isCompleted()) {
                if (status.isRollbackOnly()) {
                    transactionManager.rollback(status);
                } else {
                    transactionManager.commit(status);
                }
            }
        }
    }
}


关于事务封装

【问题说明】
手动控制事务的代码,存在大量冗余,下面的这几句代码,全局可以搜到多处,应该是研发人员拷贝了其他人的代码。
image.png
无独有偶,类似的还有下面的用法,应该是拷贝其他代码直接过来。
image.png

【改进说明】
涉及重复的代码,应该考虑抽离公用部分封装一个方法,这样后续的好处如下:

  • 研发可以直接试用公用方法,而不是选择拷贝大量代码;
  • 后续发现代码问题,对代码的修改也集中在公用部分;
  • 可以在公用方法上做一些扩展和封装,例如事务正常关闭;

Tips:程序猿允许拷贝代码,但在拷贝之前要先熟悉用法背后的原理,同时考虑一下复用性。

【封装手动控制事务示例】

public class TransactionUtils {

    @Autowired
    private DataSourceTransactionManager transactionManager;

    /**
     * 执行事务操作
     *
     * @param transactionalCode 执行事务操作的逻辑,可以是一个 Lambda 表达式或者方法引用
     * @param isolationLevel    事务隔离级别
     * @param <T>               返回值类型
     * @return 事务操作的返回值
     */
    public <T> T doInTransaction(TransactionCode<T> transactionalCode, int isolationLevel) {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(isolationLevel);
        def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);

        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 执行事务操作
            T result = transactionalCode.execute();

            // 提交事务
            transactionManager.commit(status);

            return result;
        } catch (Exception e) {
            // 发生异常,则回滚事务
            transactionManager.rollback(status);
            throw e; // 抛出异常
        }
    }

    /**
     * 定义一个函数式接口,用于执行事务操作的逻辑
     *
     * @param <T> 返回值类型
     */
    @FunctionalInterface
    public interface TransactionCode<T> {
        T execute();
    }
}

public class TransactionalService {

    private TransactionUtils transactionUtils;

    public TransactionalService(TransactionUtils transactionUtils) {
        this.transactionUtils = transactionUtils;
    }

    public void someTransactionalMethod() {
        transactionUtils.doInTransaction(() -> {
            // 在这里执行你的事务逻辑
            // 返回值可以是任何类型,根据需要自行定义
            return null;
        }, DefaultTransactionDefinition.ISOLATION_DEFAULT);
    }
}

问题排查经验

这次出现问题后,除了代码写法的注意事项,还有就是遇到问题时排查定位的经验分享。
回过头来,可以总结一下哪些是我们可以快速定位问题的宝贵经验。
1、出现死锁问题,第一时间要找DBA积极配合,无论是导致死锁的语法,还是事务错乱的回滚情况,DBA这边的情况是很重要的,尤其是每天卡顿的第一时间产生死锁的语法;
2、遇到线上问题,灵活借用Prometheus、Grafana、Skywalking、Zipkin、Kibana、SpringBootAdmin、Nacos、Rancher、应用日志等工具,知道每个工具能做什么。例如使用Prometheus定位程序线程数,工程反馈每次需要重启恢复,死锁杀不完,其实可以往这方面验证;利用Kibana可以查应用日志看不到的更多信息;利用Skywalking和Zipkin可以定位到全链路更多信息;当然,最重要的还是应用日志的使用,这是基础功能,研发人员必备;
3、代码提交记录很关键,优先定位出现卡顿问题开始的那天,之前一段时间的代码提交和发布记录,越拖越久,越难定位;


事务使用经验

Tips:问题排查的手段,毕竟是基于问题已经产生情况下的兜底操作,需要耗费较大的代价,程序猿还是尽量把问题消灭在编码阶段。

上面说了一大堆,关于声明式事务和编程式事务,没必要去纠结好坏,也不能一捆子打死必须都用@Transactional
像复杂的业务必须使用手动控制事务的方式,更加灵活,技术没有好坏,还是用法问题。
目前来看,部分业务经常使用for update行锁机制,在事务控制下,编码不科学出现锁表的可能性较大,如何做好复杂事务的控制,减低锁表概率?

避免长时间的事务阻塞

  • 合理设计事务边界是避免锁表的关键,事务应该尽可能地短小,仅在必要时才持有数据库锁,长时间的事务可能导致数据库锁的持有时间过长,增加了锁表的风险。因此,将事务的范围限制在必要的最小范围内是非常重要的。
  • 避免长时间的查询操作,可以的话,查询可以放在事务外,只有必要动作才在事务中完成。
  • 避免在事务中使用远程调用,这部分耗时是不可控的。
  • 尽量少用嵌套或挂起事务,使用也要保障子事务耗时。
  • 适当选择@Transactional(timeout = 30)指定事务超时时间。
  • 如果使用@Transactional,事务所在方法也不宜过大,不适合在复杂方法上、甚至整个接口逻辑上,直接加事务。
  • 。。。

尽量保障锁表的顺序
在编写事务代码时,注意控制加锁的顺序,尽量按照固定的顺序对数据进行加锁。确保在事务中对数据进行操作时,按照相同的顺序加锁和解锁,避免出现死锁或锁表的顺序问题。

避免跨事务的查询操作
尽量避免在一个事务中执行长时间的查询操作,而在另一个事务中更新相同的数据,这样容易导致锁表的情况发生。如果需要跨事务操作,可以考虑使用乐观锁或者分布式锁来控制并发访问。

其他事务措施

  • 不要局限于数据库锁,可以灵活搭配使用分布式锁等其他机制;
  • 更精准的行锁,减少锁的数据范围;
  • 。。。

总结陈词

上文分享了某次事务用法导致的故障,针对故障做了排查总结和知识沉淀,关于事务更详细的用法,后面再专栏补充。

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1917837.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS2019使用C#写窗体程序技巧(1)

1、打开串口 private void button1_Click(object sender, EventArgs e){myPort cmb1.Text;mybaud Convert.ToInt32(cmb2.Text, 10);databit 8;parity Parity.None;stopBit StopBits.One;textBox9.Text "2";try{sp new SerialPort(myPort, mybaud, parity, dat…

蓝卓创始人褚健:工业互联网平台技术赋能中小企业数字化转型的实施路径

工业4.0是由工业软件驱动的工业革命&#xff0c;与传统厚重的工业软件不同&#xff0c;supOS就好比嵌入工厂的“安卓系统”。如果把一个工厂当作一台手机&#xff0c;因为有安卓或苹果开放的操作系统&#xff0c;吸引了全世界聪明的人开发了大量APP供人们使用&#xff0c;手机才…

java:将集合中的数据放到文件中

代码实现目标&#xff1a; 将集合中的数据写道文件中通过字符缓冲输出流实现 代码展示 public static void main(String[] args) throws IOException {//创建ArrayList集合ArrayList<Student> array new ArrayList<>();//创建学生对象Student s1 new Student(&…

昇思25天学习打卡营第14天|基于MindSpore的红酒分类实验

背景介绍 本文主要介绍使用MindSpore在部分wine数据集上进行KNN实验。 K近邻算法原理 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;最初由 Cover和Hart于1968年提出(Cover等人,1967)&#xff0c;是机器学习最…

IPv4到IPv6的转换

为何要向IPv6过渡&#xff1a; 随着互联网的飞速发展&#xff0c;越来越多的设备接入网络&#xff0c;IPv4地址资源日益匮乏&#xff0c;已无法满足不断增长的需求。 IP地址定位&#xff1a;IP数据云 - 免费IP地址查询 - 全球IP地址定位平台 IPv6的出现为解决这一问题提供了…

基于OOB的NFTL设计

Nand flash设备存储结构示例 上图是一个1056Mb的存储设备。页面用户数据 空间是2KB&#xff0c;OOB是64字节&#xff0c;每个块 包含64个页面&#xff0c;一共 1024个块。用户数据 空间是128MB&#xff0c;OOB空间是4MB。 每个页面的OOB保留一个字节 用于坏块 标识 &#xff0c…

Elasticsearch 更新指定字段

Elasticsearch 更新指定字段 准备条件查询数据更新指定字段更新子级字段 准备条件 以下查询操作都基于索引crm_clue来操作&#xff0c;索引已经建过了&#xff0c;本文主要讲Elasticsearch更新指定字段语句&#xff0c;下面开始写更新语句执行更新啦&#xff01; 查询数据 查…

Flat Ads:金融科技应用的全球化趋势与发展前景

近年来,全球金融应用市场遭遇了重大严峻考验与深刻变革,但即便在全球经济承受重压、市场波动加剧的背景下,金融科技应用仍展现出了强大的韧性与蓬勃的增长动力。相关机构预计,2023 年全球金融应用市场的总收入达到 15.5亿美元的新高,实现了同比19%的显著增长,而到2027年,这一数…

【源码+文档+调试讲解】超市进销存管理系统

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

探索 ASTRA.AI:打造低代码多模态 AI 应用的开源平台

声网&#xff08;Agora&#xff09;研发的 ASTRA 平台&#xff0c;作为一款面向大语言模型应用开发的开源解决方案&#xff0c;无疑为 AI 领域注入了新的活力。它巧妙地结合了 BaaS&#xff08;后端即服务&#xff09;概念与大型语言模型的运营&#xff0c;使得创建高性能的生成…

开发情绪识别人工智能时的道德考量

情绪调节人工智能是机器学习领域的最新技术进步之一。尽管它显示出巨大的潜力&#xff0c;但道德问题将影响其采用率和寿命。人工智能开发人员能克服这些问题吗&#xff1f; 什么是情绪识别人工智能&#xff1f; 情绪识别人工智能是一种机器学习模型。它通常依赖于计算机视觉…

AI算力中心研究分析

中国 AI 算力中心研究报告 算力产业稳健发展&#xff0c;算力创新能力持续增强&#xff0c;推动我国数字经济量质齐升。 2022 年我国算力规模稳步扩张&#xff0c;算力发展为拉动我国 GDP 增长做出突出贡献&#xff0c;在 2016-2022 年期间&#xff0c;我国算力规模平均每年增…

基于springboot+vue的文件管理系统

一、系统架构 前端&#xff1a;vue2 | element-ui 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.8 | mysql | maven | node 二、代码及数据库 三、功能介绍 01. 注册 02. 登录 03. 管理员-首页 04. 管理员-个人中心-修改密码 05. …

Idea 2023 使用GitLab提交代码提示输入Token

项目场景&#xff1a; 今天电脑换硬盘了&#xff0c;安装了 IDEA2023 款的社区版开发工具&#xff0c;下载代码后&#xff0c;提交并推送代码到远程服务器的时候&#xff0c;提示输入Token&#xff0c;并不是用户名和密码。 问题描述 推送代码到远程GitLab本地仓库的时候&…

通用代码生成器模板体系,域对象,枚举和动词算子

通用代码生成器模板体系&#xff0c;域对象&#xff0c;枚举和动词算子 通用代码生成器或者叫动词算子式通用目的代码生成器是一组使用Java编写的通用代码生成器。它们的原理基于动词算子和域对象的笛卡尔积。它们没有使用FreeMarker和或者Velocity等现成的文件式模板引擎。而…

php快速入门

前言 php是一门脚本语言&#xff0c;可以访问服务器&#xff0c;对数据库增删查改&#xff08;后台/后端语言&#xff09; 后台语言&#xff1a;php&#xff0c;java&#xff0c;c&#xff0c;c&#xff0c;python等等 注意&#xff1a;php是操作服务器&#xff0c;不能直接在…

曝宝马汽车门店亏损严重价格战带来的伤害太大了

今年以来不仅餐饮行业难,就连一些车企都陷入困境当中,多家车企选择打价 格战。只不过日前的时候媒体爆料称,宝马汽车门店因为打价格战,最终亏损严 重,为了避免亏损再度出现,因此宝马7月将会开始降量保价。文章来源于&#xff1a;股城网www.gucheng.com 实际上,进入2024年…

分享语音音浪的自制动态特效

主要使用小程序实现的&#xff1a; <!-- wxml --> <view class"audio"><view class"audio-item" wx:for"{{list}}" wx:key"index" style"{{item}}"></view> </view> //js list: [animation…

Sentieon Arm版本:进一步降低基因组计算成本

前不久&#xff0c;Arm在其社区的HPC blog上发布了一篇Sentieon在低通量全基因组&#xff08;LP-WGS&#xff09;的应用案例。 图1 伴随着大规模基因组学的需求持续增长&#xff0c;基因测序成本的降低使得研究和分析更加广泛。而在基因组学的每一个应用背后,都有一系列计算密…

C++对顶堆(求第k大、k小数)+P1801题解

这里借鉴了一些别人题解的思路&#xff0c;仅供自己收藏使用。 题目大意&#xff1a;动态修改数组&#xff0c;求第k小的数。 from&#xff1a;7KByte P1801题解 对此&#xff0c;POISONN大佬发表了他的意见&#xff1a;楼主写法应该是让大根堆里有k-1个元素&#xff0c;然后…