news 2026/10/18 20:20:59

Spring refresh()源码导读:从IoC容器初始化到Bean生命周期

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring refresh()源码导读:从IoC容器初始化到Bean生命周期

我先说个结论:Spring的refresh()方法,是所有Spring面试题的最大公约数。不管是问IoC原理、Bean生命周期、三级缓存、@Autowired怎么生效,还是问你项目启动时到底发生了什么,追到最后都会落到AbstractApplicationContext.refresh()这一个方法上。方法名叫“刷新”,但干的是整个Spring容器的初始化——创建BeanFactory、加载BeanDefinition、执行后处理器、实例化所有单例Bean、发布事件,一条龙全在这里。我见过不少同事,包括我自己最开始读Spring源码的时候,都是从这个方法切入,然后在各种doXXX和registerXXX之间迷路,一上午过去脑袋里只剩一堆方法名。这篇文章就把我自己反复摸爬滚打出来的阅读技巧整理一遍,目标是让刚开始读源码的人也能顺着refresh()这条线,把Spring容器启动过程真正走通。

1. 为什么所有Spring面试题最终都会落在refresh()上

1.1 refresh()是IoC容器的“总导演”

先看一个最简单的启动方式:

public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); }

点进AnnotationConfigApplicationContext这个构造器,你会发现它做了两件事:先调用无参构造器初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner,然后调用register(componentClasses)把配置类注册成BeanDefinition,最后一行就是refresh()。

如果是老项目用的ClassPathXmlApplicationContext,构造器最后也是refresh()。到了Spring Boot,SpringApplication.run()内部创建好ApplicationContext之后,进入的仍然是AbstractApplicationContext.refresh(),只是中间多了一层refreshContext()。也就是说,不管你是注解驱动、XML驱动还是Spring Boot,最终启动入口都是这一个方法。

所以读refresh(),不只是读一个方法,而是拿到了整个IoC容器的执行地图。你以后看到任何关于“Bean什么时候创建”“配置类什么时候解析”“AOP切面什么时候生效”的疑问,都可以在这张地图上定位到具体坐标。

1.2 阅读能力的三层分水岭

我自己把读refresh()的水平分成三层:

  • 第一层:能背出方法里12个步骤的名字,知道每步大概干嘛。
  • 第二层:能说清楚invokeBeanFactoryPostProcessors和finishBeanFactoryInitialization这两个核心步骤内部的关键分支,尤其是后处理器的执行顺序。
  • 第三层:能结合实际问题定位源码,比如循环依赖为什么三级缓存能解,为什么@Autowired能识别ApplicationContextAware这类接口。

这篇文章的定位,是帮你从第一层扎实走到第二层,并且给第三层铺好路。我不打算把每行代码都抄出来讲,而是讲“怎么读”比“读到了什么”更重要的那些方法。

2. 读源码前的三件套:环境、断点、目标

2.1 源码怎么拉、怎么导入才不劝退

很多人读Spring源码第一步就卡住了:用Maven下载的sources.jar虽然能看代码,但注释残缺,跳转体验也差,方法多了以后根本理不清。我的建议是直接拉GitHub上的源码工程,自己导入IDEA。

git clone https://github.com/spring-projects/spring-framework.git cd spring-framework git checkout v5.3.39

注意,spring-framework由Gradle管理,不是Maven。IDEA导入时选择Gradle项目,等待依赖下载完就行。JDK版本建议8或11,太新的JDK反而可能在编译工具链上出幺蛾子。

如果你平时用的是Spring Boot 2.7.x,对应Spring Framework 5.3.x;如果用Boot 3.x,那Framework是6.x。我建议第一次读选5.3.x,网上讨论最多、资料最全,坑也都被前人踩得差不多了。

提示:源码工程里有很多模块,你只需要关注spring-context、spring-beans这两个模块的源码,其他如spring-web、spring-jdbc暂时不用管。IDEA里对着AbstractApplicationContext直接跳转,也可以。

2.2 第一个断点应该打在哪

环境准备好以后,不要急着到处点。建一个最普通的Java工程,引入你本地源码模块,然后写一个最小启动类:

@Configuration @ComponentScan("com.demo") public class AppConfig { }
public class DemoApplication { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.close(); } }

在AbstractApplicationContext.refresh()方法的第一行打个断点,然后以Debug模式运行。停住之后,看一下IDEA左下角的调用栈:AnnotationConfigApplicationContext的构造器在调用refresh(),而底层到AbstractApplicationContext。这个栈帧就是你的“锚点”,接下来所有追踪都从这个锚点出发。

从断点开始,我建议你开启“Step Over”模式,一步一步看refresh()内部方法的调用顺序,先别用“Step Into”钻进去。这一步的目标是建立“顺序感”。等你把12个步骤的名字都混了个脸熟,再决定往哪个方法里钻。

2.3 每次阅读只带一个目标

读源码最忌讳的就是想一口气全看懂。我的习惯是每次打开refresh()之前,先给自己定一个小目标:

  • 第一次:只看主流程,12个步骤分别干什么。
  • 第二次:只跟finishBeanFactoryInitialization这个单例创建链路。
  • 第三次:只关注invokeBeanFactoryPostProcessors的后处理器调度顺序。

一次一题,读不懂就停,改天再来。带着目标读,比漫无目的扫代码高效得多。

3. refresh()十二步:先背框架,再逐个击破

3.1 十二步对照表

先把refresh()的主体结构砸出来。Spring 5.3.x里,AbstractApplicationContext.refresh()大致的流程如下,我配上了一句话作用说明:

步骤方法名一句话作用
1prepareRefresh()记录启动时间、设置容器状态、初始化属性源、收集早期监听器
2obtainFreshBeanFactory()创建/刷新BeanFactory,并加载BeanDefinition(如XML场景)
3prepareBeanFactory(beanFactory)为BeanFactory配置类加载器、SpEL解析器、Aware回调等
4postProcessBeanFactory(beanFactory)模板方法,留给子类扩展(比如Web容器相关)
5invokeBeanFactoryPostProcessors(beanFactory)执行BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor
6registerBeanPostProcessors(beanFactory)注册所有BeanPostProcessor,但此时不执行
7initMessageSource()初始化国际化消息源
8initApplicationEventMulticaster()初始化事件多播器
9onRefresh()模板方法,子类扩展(如Web服务器启动)
10registerListeners()把监听器Bean注册到多播器,并广播早期事件
11finishBeanFactoryInitialization(beanFactory)实例化所有非懒加载单例Bean,核心步骤
12finishRefresh()初始化生命周期处理器,发布ContextRefreshedEvent

后面还有catch (BeansException ex)和finally { resetCommonCaches(); }。异常分支干的是销毁已经创建的Bean,避免容器处于半初始化状态;finally清理的是反射缓存、ResolvableType缓存这些元数据。

这张表值得先记下来。不用死记,读两遍源码再看这张表,自然就记住了。

3.2 前三步:准备、拿工厂、做“出厂设置”

第1步prepareRefresh()相对简单,核心是几点:设置容器启动时间、把closed和active标志位翻转、初始化PropertySources占位符、准备一组earlyApplicationListeners。它有一个模板方法initPropertySources(),默认空实现,留给子类填充环境变量。阅读时你只需要知道“这一步是热身”,代码本身不值得逐行盯。

第2步obtainFreshBeanFactory()值得重点看一眼,因为它是“BeanFactory从无到有”的关键。AbstractApplicationContext里这个方法很短,是个模板方法骨架,具体实现取决于子类:

  • GenericApplicationContext的refreshBeanFactory()里会new一个DefaultListableBeanFactory,然后调用loadBeanDefinitions(beanFactory)。
  • AbstractRefreshableApplicationContext还会设置allowBeanDefinitionOverriding、allowCircularReferences这些标志位。

这里有一个很反直觉的点:refresh()方法本身并没有“创建BeanFactory”的逻辑,真正创建是子类干的。所以读refresh()时,很多代码其实是“多态调用”,阅读时别把obtainFreshBeanFactory()当成一个简单getter,它是模板方法。

第3步prepareBeanFactory(beanFactory)是做“出厂设置”的。它给BeanFactory设置了类加载器、SpEL表达式解析器、占位符解析器,然后注册了一堆“手动指定依赖”的组件:

beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); beanFactory.registerResolvableDependency(ResourceLoader.class, this); beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this); beanFactory.registerResolvableDependency(ApplicationContext.class, this);

同时还会ignoreDependencyInterface(EnvironmentAware.class)、ignoreDependencyInterface(ApplicationEventPublisherAware.class)等等。这个ignore的意思是:当Spring准备给Bean做属性注入时,遇到这些Aware接口,不要走自动装配,而是由容器在之后用回调方式显式设置。这就是为什么你的ApplicationContextAware接口能拿到容器引用,而不是靠@Autowired塞进来。

阅读技巧:第3步不需要把每一行注册都背下来,只需要明白它是在“调教”BeanFactory,让它具备Spring容器该有的各种基础设施能力。

3.3 第5、6步:两个“后处理器”的分水岭

invokeBeanFactoryPostProcessors和registerBeanPostProcessors是12步里最难啃的两块。它们名字很像,很多人读完就混了。我提供一个区分角度:

  • invokeBeanFactoryPostProcessors:立刻执行。这些后处理器专门用来修改BeanDefinition,比如ConfigurationClassPostProcessor就是在这里解析@Configuration、@ComponentScan、@Bean的,因此BeanDefinition真正的内容大多在这时候才补全。
  • registerBeanPostProcessors:只注册、不执行。它把所有BeanPostProcessor实例放进BeanFactory的一个list里,等后面createBean时才会被逐个调用,用来干预Bean实例化过程。

记住一句话:一个管“定义”,一个管“实例”。这两个步骤的具体阅读方法,我放在第4章专门展开,这里先把位置和分工搞清楚。

3.4 第7到10步:消息、事件和Web服务器

第7步initMessageSource(),看一眼就行。它先从BeanFactory里找有没有用户自定义的MessageSource,没有就用DelegatingMessageSource兜底。

第8步initApplicationEventMulticaster()同理。优先用用户自定义的ApplicationEventMulticaster,没有就new一个SimpleApplicationEventMulticaster。注意这里有个细节:多播器会尝试从BeanFactory里拿TaskExecutor,如果有就把异步执行器塞进去,这样事件就能异步发布。自己业务里想要异步事件,回头可以往这个方向配。

第9步onRefresh()是模板方法,Web场景下子类会在这里启动内嵌Tomcat/Netty。纯注解容器里它是空实现。

第10步registerListeners(),把容器里实现了ApplicationListener接口的Bean注册到多播器。它还会把prepareRefresh()阶段收集的早期事件(earlyApplicationEvents)先缓存起来,等后续finishRefresh()发布ContextRefreshedEvent再一并分发。

阅读技巧:这四步不是核心矛盾,快速扫过即可。如果你本来就不关心事件机制,可以直接跳到第11步,后面前后文也不会有太大割裂感。

3.5 第11、12步:真正的主菜和收尾甜点

第11步finishBeanFactoryInitialization(beanFactory)是全文最重要的地方。它做两件事:

  1. 给BeanFactory设置类型转换器ConversionService和字符串解析器EmbeddedValueResolver。
  2. 冻结Bean定义(freezeConfiguration()),然后调用preInstantiateSingletons()实例化所有非懒加载的单例Bean。

preInstantiateSingletons()会遍历所有BeanDefinition,跳过抽象类、跳过懒加载和原型Bean,然后对每个单例Bean调用getBean(beanName)。也就是说,你业务里大部分@Service、@Component、@Bean单例对象,都是在这一步被真正new出来的。

第12步finishRefresh()是收尾:清理JVM一级缓存中的资源类、初始化LifecycleProcessor、调用onRefresh()(这一步是web容器启动逻辑的位置)、发布ContextRefreshedEvent。

提示:事件发布的顺序很有讲究。必须在所有单例Bean都创建完才发ContextRefreshedEvent,否则监听器回调时想依赖其他Bean可能还没创建好。

4. 最值得死磕的三个子流程:别人不会告诉你的那些点

4.1 invokeBeanFactoryPostProcessors:先看调度顺序,再看内部实现

这是refresh()里最劝退的一个方法,因为点进去以后你会看到大量循环和递归。我第一次读时一路Step Into,直接进了ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry,然后又跳进一个内部类的几百行代码里,人懵了。

后来我换了策略:第一遍只看“入口调度顺序”,绝不深入任何具体PostProcessor的实现。

入口调度顺序大致是这样的:

  1. 先收集所有BeanDefinitionRegistryPostProcessor类型的Bean。
  2. 区分PriorityOrdered、Ordered、普通无序三类,按顺序依次执行它们的postProcessBeanDefinitionRegistry()方法。
  3. 执行完后还会再次从BeanFactory里扫描,因为前一轮处理可能注册了新的BeanDefinitionRegistryPostProcessor,若有则继续循环执行。
  4. 全部BeanDefinitionRegistryPostProcessor执行完毕,最后才统一执行普通BeanFactoryPostProcessor的postProcessBeanFactory()。

这里为什么容易晕?因为postProcessBeanDefinitionRegistry()执行过程中,往往又会产生新的后处理器,Spring需要反复重新扫描,所以代码里才有while循环和repeat标志。你如果跟着某一轮递归进去看细节,就很容易丢掉“当前处于第几轮”这个上下文。

阅读技巧就是:先画一张“调度顺序图”,把PriorityOrdered → Ordered → 普通这个套路记下来,然后随便找一个具体的PostProcessor实现去验证,比如ConfigurationClassPostProcessor。等你验证完一个,这条“调度顺序”就算彻底吃透了。

4.2 registerBeanPostProcessors:顺序就是生命

这个方法比上一个温和,但同样有排序逻辑。它的执行顺序是:

  1. 注册实现PriorityOrdered接口的BeanPostProcessor。
  2. 注册实现Ordered接口的。
  3. 注册普通无序的。
  4. 注册MergedBeanDefinitionPostProcessor(用于处理@Value、@Autowired、@PostConstruct等注解的元数据合并)。
  5. 最后注册一个ApplicationListenerDetector,用来把实现了ApplicationListener接口的Bean自动登记成监听器。

你可能发现规律了:Spring在处理多个同类型组件时,特别喜欢用“PriorityOrdered → Ordered → 无排序”的套路。这个套路在invokeBeanFactoryPostProcessors里出现过,在registerBeanPostProcessors里又出现,后面你读MBeanExporter之类组件时还会见到。

阅读时我建议你找几个具体的后处理器看看它们的order值:

  • AutowiredAnnotationBeanPostProcessor:负责@Autowired和@Value注入,排序优先级很高。
  • CommonAnnotationBeanPostProcessor:负责@Resource、@PostConstruct、@PreDestroy。
  • ApplicationListenerDetector:排在最后。

为什么顺序重要?因为BeanPostProcessor在Bean创建过程中是链式调用的,前面的后处理器先把Bean加工一波,后面才能继续加工。顺序错了,注入时机就乱了。你只要理清“谁先谁后”,很多Bean初始化诡异问题都能定位到“某个后处理器没生效”上面。

4.3 finishBeanFactoryInitialization:三级缓存到底在哪一刻生效

死磕完两个后处理器,主菜就是finishBeanFactoryInitialization。这个方法的链路是:

preInstantiateSingletons() -> getBean(beanName) -> doGetBean(beanName) -> getSingleton(beanName) // 查三级缓存 -> singletonFactory.getObject() // 缓存都没有,才走createBean -> createBean(beanName) -> doCreateBean(beanName)

很多人读到这里又迷糊了,因为doGetBean非常长。我的建议是:这条链路里,你先只盯DefaultSingletonBeanRegistry这个类,把三级缓存的读写时机搞清楚,再往doCreateBean里钻。

三个缓存的字段定义在DefaultSingletonBeanRegistry:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • singletonObjects:一级缓存,存放已经完整创建好的单例Bean。
  • earlySingletonObjects:二级缓存,存放“提前暴露”的半成品Bean,这些Bean已经实例化但尚未完成属性填充/初始化。
  • singletonFactories:三级缓存,存放的是ObjectFactory,调用它的getObject()可以拿到早期引用。

阅读时在getSingleton(String beanName, boolean allowEarlyReference)上打条件断点,条件可以写beanName.equals("userService")。你会看到以下场景:

  1. 第一次getSingleton调用,三级缓存全都没命中,返回null。
  2. 进入doCreateBean,实例化对象后,会调用addSingletonFactory(beanName, () -> getEarlyBeanReference(...)),这个lambda被放进三级缓存。此时Bean是个“半成品”。
  3. populateBean填充属性时,如果发现要注入的对象还没创建完,就会触发其他Bean的创建,形成循环。循环发生时,其他Bean能通过三级缓存的ObjectFactory拿到这个半成品的早期引用,于是塞进二级缓存。
  4. 最终当前Bean完成初始化,调用addSingleton(beanName, singletonObject)放进一级缓存,同时清掉二三级缓存。

这个机制困扰我很久的是“为什么需要三级缓存,而不是直接二级”。关键就在三级缓存存的是ObjectFactory,不是实例本身。由于ObjectFactory.getObject()可以返回经过AOP代理包装后的早期对象,Spring才能保证循环依赖发生时,注入的是代理而非裸对象。你顺着这个思路去读getEarlyBeanReference和AbstractAutoProxyCreator的getEarlyBeanReference重写,就能把AOP预演和循环依赖串起来。

阅读技巧总结:在doCreateBean里只关注三处——实例化后addSingletonFactory之前的代码、populateBean入口、addSingleton之前的状态。其他地方(比如各种applyMergedBeanDefinitionPostProcessors)这轮可以先跳过。

5. 踩坑实录:读源码时最容易掉的三个坑

5.1 钻进递归黑洞,忘了自己在哪

invokeBeanFactoryPostProcessors内部实现invokeBeanDefinitionRegistryPostProcessors时,会循环调用,还会嵌套处理dependsOn。很多人在这一步点着点着就彻底忘了自己是从哪进来的。

我的对策很简单:每次进入一个方法前,先问自己三句话——谁在调用我?我处于第几轮循环?处理的是哪个Bean?

IDEA的Debugger里,线程栈的“栈顶”通常是你正在执行的方法,“栈底”是线程的起点。你需要关注的是从refresh()到你当前位置之间的这几帧,不要管栈更深处Spring内部的一些机械操作。如果发现当前帧离refresh()已经隔了七八层,大概率你钻得太深了,先回到refresh()这一帧,重新按Step Over走一遍。

5.2 只读正常分支,忽略异常分支

读源码有个天然的坏习惯:跟着快乐路径走,看到if (xxx) return就跳过。但doGetBean里大量逻辑恰恰藏在条件分支里。比如:

if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { return createBean(beanName, mbd, args); }); }

如果之前缓存里有,这个方法直接就返回了。可“没有缓存”的情况才更关键。我建议你阅读时看到if,先想想“如果条件不满足,下一行是什么”,强迫自己至少把分支的另一侧扫一眼。比如allowCircularReferences为false时,循环依赖会直接抛BeanCurrentlyInCreationException,这个分支你不看,后面排查“循环依赖为什么不生效”就会一头雾水。

5.3 版本漂移:网上文章对不上号

Spring Framework迭代很快。5.3.x和6.x之间,不仅包名从javax.换成jakarta.,内部代码结构也有变化。比如6.x里删掉了一些过时的*Aware接口注册逻辑,DefaultSingletonBeanRegistry的三级缓存实现虽然大体保留,但细节已经调整。

如果你照着网上2020年的文章去读6.x源码,很可能找不到对应类名或方法位置。记住一件事:读源码前先确认版本,最好以你项目实际依赖的Spring版本为准。你项目用的是Boot 2.7,就切到Framework 5.3.x;项目用Boot 3.x,就切到6.x。旧文章当思路参考,不要当代码字典。

6. 读完之后,refresh()给你留下什么

把refresh()读完以后,你再回头看那些面试题,会有一种“题都变透明了”的感觉:

  • 控制反转发生在哪?obtainFreshBeanFactory创建工厂,invokeBeanFactoryPostProcessors解析BeanDefinition,finishBeanFactoryInitialization实例化Bean——整个过程都是容器在替我们管理对象。
  • 三级缓存什么时候写入?不是一开始写,而是对象实例化后、属性填充前,通过addSingletonFactory写入。
  • @Autowired什么时候生效?在createBean的populateBean阶段,由AutowiredAnnotationBeanPostProcessor等后处理器完成。
  • 手写Spring应该参考什么?无非是:一个BeanDefinition注册表、一个单例池、一个后处理器列表、一个事件多播器,然后按refresh()的顺序把它们串起来。

如果看完脑子里还是乱,我建议你分三次再读一遍:

第一次只跟主流程,把12个步骤名字背下来; 第二次跟finishBeanFactoryInitialization,重点看getSingleton和addSingleton; 第三次跟invokeBeanFactoryPostProcessors,只看调度顺序,不看内部实现。

每次阅读给自己设定一个明确的小问题,比如“BeanFactory是哪里new出来的”“BeanPostProcessor是在哪一步被注册的”“循环依赖的早期引用从哪里拿到的”。读完之后合上源码,用自己的话把流程讲一遍,卡住的点就是下次要回去重新看的地方。

我自己到现在每次遇到容器启动相关的诡异问题,仍然会先回到refresh()这张地图上定位坐标,而不是漫无目的地全局搜索。这个方法的可靠性,远比记住几行源码高得多。希望这篇阅读技巧,也能让你下次打开Spring源码时不再发怵。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 17:58:28

本地部署代码智能助手:从环境配置到编辑器集成的完整指南

1. 为什么要在本地跑一个代码智能助手1.1 从“补全”到“对话式编程”的转变这两年写代码的方式变化挺大的。早几年大家用编辑器自带的补全&#xff0c;敲几个字母弹出一串候选&#xff0c;那已经觉得很方便了。后来出现了基于大模型的代码助手&#xff0c;能根据上下文整段整段…

作者头像 李华
网站建设 2026/10/18 14:36:19

严蔚敏数据结构C语言代码实现:环境配置、常见坑与调试技巧

简介&#xff1a;严蔚敏《数据结构与算法&#xff08;C语言版&#xff09;》配套代码实现合集&#xff0c;主要面向计算机专业学生、考研复习者与需要补算法基础的C语言开发者。资源以教材章节为主线&#xff0c;覆盖线性表、栈、队列、二叉树、图、查找、排序、递归与分治等核…

作者头像 李华
网站建设 2026/10/18 20:20:02

VS Code开发环境基建:下载校验、插件沙箱与企业级部署

1. 这不是“装个编辑器”那么简单&#xff1a;VS Code下载与插件配置的本质是开发环境基建工程很多人点开这个标题&#xff0c;第一反应是&#xff1a;“不就是去官网下个安装包&#xff0c;再点几下鼠标装几个插件吗&#xff1f;值得写一篇长文&#xff1f;”——我完全理解这…

作者头像 李华
网站建设 2026/10/16 7:26:03

PostgreSQL事务核心机制:提交、回滚与保存点实战详解

上周帮一个朋友排查线上问题&#xff0c;场景特别常见&#xff1a;订单表里已经写了扣库存记录&#xff0c;但订单状态却显示创建失败。折腾了一下午&#xff0c;最后发现根因就一句话——程序里开启事务的代码写错了分支&#xff0c;扣库存那条SQL根本没有被包进同一个事务里。…

作者头像 李华
网站建设 2026/10/17 4:44:52

从能跑到无可挑剔:打造impeccable代码的工程实践指南

1. 从一个词出发&#xff1a;为什么"impeccable"值得单独拿出来聊第一次看到"impeccable"这个词被当作一个项目标题&#xff0c;我愣了一下。它不是一个技术名词&#xff0c;不是框架名&#xff0c;也不是某个工具库的缩写&#xff0c;而是一个纯粹的英文形…

作者头像 李华
网站建设 2026/10/18 19:22:20

SQL性能优化实战:慢SQL排查、执行计划分析与索引设计指南

1. 一条慢SQL引发的思考&#xff1a;优化到底在优化什么先说个我自己的真实经历。几年前接手过一个内部报表系统&#xff0c;数据量大概几百万行&#xff0c;不算夸张&#xff0c;但每天凌晨的统计任务总是跑不完&#xff0c;经常早上上班一看&#xff0c;报表还是空的。当时第…

作者头像 李华