news 2026/10/22 5:53:51

分治算法详解:从递归边界到复杂度分析的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
分治算法详解:从递归边界到复杂度分析的实战指南

分治算法(Divide and Conquer)是我心里最值得先搞懂的一种算法设计思想,没有之一。排序、查找、大数据框架里的并行处理,底层都能看到"把大问题拆成小问题、分别解决、再合并结果"的影子。你可以把它类比成管理一个团队:一个人干不完的活,拆成小组各自执行,最后把各组的成果汇总成完整结果——这就是分治最朴素的样子。

不过真到写代码的时候就会发现,"拆"只是第一步。边界条件怎么收?子问题的结果怎么合并?递归会不会爆栈?这些问题不搞清楚,照着模板抄也很容易写出一个跑不起来、或者跑起来结果不对的版本。这篇文章我会从设计思路讲起,再手写拆解四个经典案例,最后整理一份实测踩坑清单。适合正在学算法的基础开发者、准备算法面试的求职者,以及想在工程里用好分治思想的工程师。

1. 分治算法的核心套路:三步拆解复杂问题

1.1 分治思想的本质:大问题为什么能变小问题

分治的标准流程是三步:分解、解决、合并。把原问题拆成若干个规模更小、结构相同的子问题;递归地解决每个子问题;再把子问题的解合并成原问题的解。这里最容易被忽略的是"结构相同"四个字——子问题必须跟原问题长一个样,只是规模变小了,否则递归就没有意义。

我特别喜欢用一个生活化的例子来讲这个思想。假设你要把一抽屉的票据按日期排序,最笨的办法是全部摊开逐个比较,数据一多就崩溃。分治的做法是:把票据平均分成两摞,每摞再分成两摞,直到每摞只剩一两张,手工排一下;然后再两两合并,合并时不断取两摞中最小的那张放到结果里,最终得到一整摞有序票据。整个过程不需要全局比较,每一层的工作量只跟本层的票据数量相关,这就是分治能把问题规模压下来的根本原因。

理解分治的时候,建议你抓住一条主线:递归树。每一次递归调用对应一个子问题,树的每一层合起来处理了全部数据规模,层数则由每次切分的比例决定。树的高度决定了递归的次数,每层的工作量决定了总开销,两者相乘才是完整复杂度。后面讲主定理时会回到这里。

1.2 分治适用的四个前提条件

不是所有问题都能用分治,能用的通常满足四个条件。我习惯把这份检查清单放在心里,每次决定用不用分治时先过一遍:

  • 问题可以分解为同构的子问题,子问题与原问题除了规模不同,其余特性一致。
  • 子问题之间相互独立,互不影响。如果子问题之间共享了大量计算,说明存在重叠子问题,那更适合动态规划,而不是纯分治。
  • 子问题的解能够合并出原问题的解。有些问题分解后虽然能解决,但合并时信息丢失,合并代价过高,分治也派不上用场。
  • 问题规模缩小到一定程度后,可以直接用朴素方法解决,这个"最小规模"就是递归边界。

这四个条件缺一不可。尤其第二个条件经常有人踩坑——看到问题可以递归拆解,就直接上分治,结果发现子问题之间重复计算太多,性能一塌糊涂。最典型的就是斐波那契数列的朴素递归,这个问题我在第4部分会专门展开。

1.3 分治降复杂度的底层逻辑:以二分查找为例

很多人学二分查找时,不知道它其实是分治思想最朴素的体现——只不过它不需要合并步骤,因为目标元素要么在左半边,要么在右半边,找到就直接返回。

用数学递推来看就清楚了。设输入规模为 n,一次比较后问题规模减半,递推式是:

T(n) = T(n/2) + O(1)

这个递推意味着每次递归只需要做常数级的比较工作,而递归树的高度是 log₂n,所以总复杂度是 O(log n)。对比线性查找的 O(n),数据量越大优势越明显。1 万个元素时,线性查找最坏要 1 万次比较,二分查找最多 14 次。

那为什么拆成两半就能节省这么多?关键在于"有效排除"。线性查找每一次比较只排除一个元素,而二分查找每一次比较排除掉一半元素。分治能降复杂度的本质,就是让每次操作尽可能多地缩减问题规模。这个逻辑放在归并排序里也同样成立:合并阶段每一层处理全部 n 个元素,但树高只有 log n 层,所以总复杂度是 n log n,而不是 n² 的冒泡式比较。

2. 分治实现中最关键的细节:边界、合并与复杂度

2.1 递归边界:最容易被忽视的"出口"

我见过太多人写递归时先写函数主体,边界条件扔到最后随手一补,结果不是死循环,就是元素被漏掉。正确的习惯是:动手写递归函数的第一个动作,就是先想清楚边界。

以归并排序为例,数组区间用 [left, right] 表示,最简单正确的边界是:

  • 如果 left >= right,说明区间里没有元素或者只有一个元素,天然有序,直接返回。

很多人会纠结到底是 left > right 还是 left >= right,这个要跟你的切分方式配套。如果你用 mid = (left + right) // 2,然后递归处理 [left, mid] 和 [mid + 1, right] 两个子区间,那 left == mid 时子区间只有单个元素,left == right 就应该停;如果你用 [left, mid - 1] 和 [mid, right] 这种切法,边界条件就要相应调整。

快速排序的边界也是同理。partition 返回枢轴位置 pivot 后,左递归区间是 [left, pivot - 1],右递归区间是 [pivot + 1, right],边界条件就是 left >= right。如果这里写错成 left > right,当区间里有两个元素而 pivot 恰好在中间时,会漏掉一个元素不处理,输出的数组仍然无序。

我的实际建议是:每写一次递归函数,先用两个最小用例在纸上走一遍。长度为 0、长度为 1、长度为 2 的输入分别会不会触发边界,会不会漏掉元素?这几分钟时间能省下后面几个小时的调试。

2.2 合并步骤决定成败:为什么归并排序是 O(n log n)

分治的三个步骤里,分解通常很容易——取个中点的计算而已;解决靠递归,也不难;最难的是合并。合并做不好,整体复杂度可能直接从 n log n 退化到 n²。

归并排序的合并函数,本质上是把两个已经各自有序的数组合并成一个有序数组。做法是两个指针分别指向两个子数组的头部,每次比较取较小者放进结果数组,直到某个子数组被取完,再把另一个剩余部分整体拷贝过去。一次合并的时间是 O(n),这个 n 是当前层两个子数组的长度之和。

如果合并写得差,比如每次合并时重新对整段做插入排序,那一层的代价就变成 O(n²),递归树所有层的总代价会高到难以接受。所以判断一个分治算法是否合格,重点是看合并这一步能不能做到与当前数据规模线性相关。

最大子数组问题也是同理。最大和的子数组只可能出现在三个位置:完全在左半边、完全在右半边、跨越中点。前两种情况递归得到,跨中点的最大子数组要从中间向左右两边分别扩展扫描,不断累加并记录最大值。这个跨中点扫描的耗时是 O(n),合并代价同样线性可控,所以整个问题的复杂度才能压到 O(n log n),而暴力的三层循环是 O(n³)。

2.3 主定理:一眼看穿分治复杂度的利器

分治算法的复杂度递推式大多长这样:

T(n) = aT(n/b) + f(n)

其中 a 是每次拆出的子问题数量,n/b 是每个子问题的规模,f(n) 是分解和合并的代价。主定理就是用来快速求解这类递推式的标准工具,分三种情况:

情况条件复杂度
情况1f(n) 小于 n^log_b(a)(多项式意义下)O(n^log_b(a))
情况2f(n) 约等于 n^log_b(a)O(n^log_b(a) * log n)
情况3f(n) 大于 n^log_b(a)(多项式意义下)O(f(n))

拿归并排序套一下:T(n) = 2T(n/2) + O(n),这里 a=2,b=2,所以 log_b(a) = log₂2 = 1,f(n) = O(n) 正好等于 n^1,命中情况2,复杂度是 O(n log n)。

再拿二分查找套一下:T(n) = T(n/2) + O(1),a=1,b=2,log_b(a) = log₂1 = 0,f(n) = O(1) 等于 n^0,命中情况2,复杂度是 O(log n)。

主定理不好记住的话,就退回递归树去理解:算一下树有几层,每层总工作量是多少,两者相乘就是总复杂度。主定理本身不会帮你写出更好的代码,但它能帮你在动手之前判断一条思路有没有前途,省下大量试错时间。

2.4 空间与调用栈:递归版和迭代版的取舍

分治依赖递归,但递归不是免费的。每次递归调用都会在栈上分配一帧,保存局部变量和返回地址。编程语言默认的递归深度限制往往比你想象中低,比如 Python 默认大约 1000 层。

归并排序的递归深度是 log n,对于 100 万数据大约 20 层,远不到栈上限;但快速排序就不一样了。如果每次 partition 都选到最差的枢轴,比如在已经有序的数组上固定取第一个元素为枢轴,递归深度会退化到 n,这时候栈溢出就真的会来敲门。

空间开销也要算清楚。归并排序的合并需要额外的临时数组,所以空间复杂度是 O(n);快速排序虽然在原数组上原地交换,但递归栈本身的最坏深度是 O(n),平均是 O(log n)。

如果你在工程里处理超大数组,有两个方向可以避免栈溢出:一是把递归改成显式的栈模拟,自己用循环和栈结构来管理待处理区间;二是注意递归深度与数据规模的关系,提前评估。实测下来,在数据量达到千万级时,Python 的递归排序明显吃力,改用迭代式归并或者直接用内置排序会更稳。

3. 四个经典案例手写拆解:从归并排序到最大子数组

3.1 归并排序:最标准的分治操作模板

先给一份最清晰的归并排序实现:

def merge_sort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 left = merge_sort(arr[:mid]) right = merge_sort(arr[mid:]) return merge(left, right) def merge(left, right): i = j = 0 res = [] while i < len(left) and j < len(right): if left[i] <= right[j]: res.append(left[i]) i += 1 else: res.append(right[j]) j += 1 res.extend(left[i:]) res.extend(right[j:]) return res

代码很简单,但它展示了分治的完整骨架:递归边界是长度不大于1的数组,天然有序;分解用切片取中点;合并用双指针逐个比较。

有几个细节值得注意。第一,合并时用的是 <= 而不是 <,这保证了排序的稳定性——相等元素的相对顺序不会改变。在很多实际场景里,稳定性比你以为的重要得多。第二,上面这份代码用切片传参,每层都会复制数组,额外开销不小;工程上更常见的是传入原始数组和左右边界,配合一个临时数组在原数组上合并,这样避免了切片带来的重复内存分配。对 Python 来说,如果你处理的是几十万以上的数据量,用切片实现的版本性能会明显差一截。

3.2 快速排序:枢轴选择与 partition 的细节

快速排序是分治思想里另一个被用烂的经典,平均复杂度同样是 O(n log n),但常数通常比归并排序小,所以实际运行往往更快。

def quick_sort(arr, left, right): if left >= right: return pivot = partition(arr, left, right) quick_sort(arr, left, pivot - 1) quick_sort(arr, pivot + 1, right) def partition(arr, left, right): pivot_val = arr[right] i = left for j in range(left, right): if arr[j] < pivot_val: arr[i], arr[j] = arr[j], arr[i] i += 1 arr[i], arr[right] = arr[right], arr[i] return i

上面用的是 Lomuto 分区法,代码短,适合讲解。它的思路是维护一个指针 i,指向比枢轴小的元素应该放置的边界位置,遍历整个区间,遇到比枢轴小的就跟 i 位置的元素交换。最终把枢轴放到 i 的位置上,左边的都小于枢轴,右边的都大于等于枢轴。

这段代码最值得品的是枢轴的选择。如果你固定取最右边的元素,而输入数组接近有序,partition 每次只能把区间分成长度为 n-1 和 0 的两部分,递归树变成一条链,复杂度退化到 O(n²)。实际工程里的解法有两种:随机取枢轴,用概率避免最坏情况;或者三数取中,取区间首、中、尾三个数的中位数作为枢轴。面试时如果被问到快速排序,主动说出枢轴选择对退化情况的影响,会是很加分的细节。

3.3 最大子数组问题:跨边界扫描的合并艺术

最大子数组问题是分治思想里很有教学价值的案例,题目是:给定一个整数数组,找一个连续子数组,使得它的元素和最大。暴力解法是枚举所有起止点,三层循环 O(n³)。分治解法则干净利落得多。

最大子数组只可能存在三个位置:完全在左半区间、完全在右半区间、跨越中间点。前两种递归解决,跨中点的需要单独扫描计算。扫描的思路是:从中间点出发向左累加,记录过程中遇到的最大和;同理从 mid + 1 向右累加,记录最大和;两部分最大和相加就是跨中点的最大子数组和。

def max_subarray(arr, left, right): if left == right: return arr[left] mid = (left + right) // 2 left_max = max_subarray(arr, left, mid) right_max = max_subarray(arr, mid + 1, right) cross_max = max_crossing(arr, left, mid, right) return max(left_max, right_max, cross_max) def max_crossing(arr, left, mid, right): left_sum = float('-inf') cur = 0 for i in range(mid, left - 1, -1): cur += arr[i] left_sum = max(left_sum, cur) right_sum = float('-inf') cur = 0 for j in range(mid + 1, right + 1): cur += arr[j] right_sum = max(right_sum, cur) return left_sum + right_sum

这个算法的递推式是 T(n) = 2T(n/2) + O(n),与归并排序完全一致,所以复杂度是 O(n log n)。很多讲解只把代码贴出来就完事了,我觉得真正值得理解的是合并步骤为什么要从中间向两边"扩散扫描"——因为跨越中点的最大子数组必须包含 mid 和 mid + 1 这两个相邻元素,所以任何跨中点的子数组都可以拆成"以 mid 为右端点的左半段"和"以 mid + 1 为左端点的右半段"的和。理解了这一点,max_crossing 的写法就顺理成章了。

3.4 二分查找:没有合并步骤的分治骨架

二分查找严格来说是分治的退化形式,因为目标元素只会出现在某一个子区间里,另一侧直接被丢弃,完全不需要合并。这种"减治"思路在算法分类里也可以归入分治一族。

经典写法如下:

def binary_search(arr, target): left, right = 0, len(arr) - 1 while left <= right: mid = left + (right - left) // 2 if arr[mid] == target: return mid elif arr[mid] < target: left = mid + 1 else: right = mid - 1 return -1

两个细节值得多说一句。第一是 mid 的计算,写成 left + (right - left) // 2 而不是 (left + right) // 2,是为了防止两个大整数相加时溢出,这在 C/C++/Java 里是真实存在的 bug 隐患。第二是循环条件用 left <= right,这个不变式看起来平淡无奇,却是所有边界派生问题的源头。面试被问"查找第一个大于等于 target 的位置""查找最后一个等于 target 的元素"时,本质上都是在这个不变式上做微调,把 left 和 right 的收缩逻辑改一改而已。

4. 实操踩坑清单:边界错位、栈溢出与重复计算

4.1 递归深度爆栈:问题往往不在递归本身

Python 默认的递归上限大约是 1000 层。我第一次处理一个十万级数据的归并排序时,直接栈溢出,排查了半天才发现是递归深度问题。

解决办法有两个。一是用 sys.setrecursionlimit() 调大上限,比如:

import sys sys.setrecursionlimit(10 ** 6)

但这个方法只是解燃眉之急,不能根治。递归深度太大还会拖慢整体性能,甚至导致进程崩溃。二是把递归实现改成迭代实现,比如用显式栈来模拟快速排序的区间划分。工程实践里,数据量超过百万级时我会优先考虑迭代版本;数据量不大的场景下,递归版本更清晰易维护,没必要为了炫技把所有递归都改掉。

另外建议动手前先估算深度:如果每次把数据对半切,10 万数据只需要约 17 层深度,绝不可能爆栈;但快速排序在有序输入 + 固定枢轴时深度接近 n,那就非常危险了。

4.2 索引与边界错位:用最小用例快速定位

分治代码里最常见的一类 bug 是索引错位:递归区间开闭不一致、mid 计算后左右区段划分出错、partition 返回位置之后左右递归区间覆盖错误。这类问题平时很难一眼发现,因为数据量大时结果乱序,你根本看不出错在哪一步。

我的调试经验是:固定用长度为 1、2、3 的最小用例逐行走一遍。比如归并排序区间是 [left, mid] 和 [mid + 1, right],我用长度为 2 的数组 [2, 1] 手算一遍,left=0,mid=0,右区间是 [1, 1],合并时分别处理两个单元素区间,这下合并逻辑有没有问题就一目了然了。

还可以在关键位置加临时打印,递归的每次进入和返回都打印当前区间和中间结果,跑一个长度为 4 的乱序数组,看递归树的每一条分支是不是都符合预期。很多时候 bug 是"区间覆盖重叠"或者"区间漏元素"造成的,打印出来之后错误模式会非常明显。

4.3 合并逻辑写错:结果对不上时的排查思路

合并阶段写错的结果通常表现为:程序能跑完,但输出数组不是全序;或者某些元素被覆盖丢失。归并排序的 merge 函数里有一个高频错误——比较两个子数组取最小值时用了 > 而不是 >=,虽然结果大体有序,但稳定性丢掉了。快速排序的 partition 里,交换指针的边界条件也经常出错,导致枢轴放错位置,左边出现大于枢轴的元素。

排查合并这类问题,我推荐一个反向验证思路:先把递归全部注释掉,只保留合并函数,手动构造两个有序数组作为输入,验证 merge 本身是否正确;确认合并没问题后,再打开递归,检查分解的区间是否正确。这样能快速定位到底是"合并写错了"还是"递归区间写错了",而不是在两者之间来回折腾。

4.4 分治退化成暴力:重复计算的识别与预防

分治最大的隐形坑,不是写错,而是选错。当子问题之间存在大量重叠时,纯递归的分治算法会退化成指数级复杂度,最典型的就是斐波那契数列的朴素递归:

def fib(n): if n <= 1: return n return fib(n - 1) + fib(n - 2)

这段代码确实是"分解 + 递归"的形式,但 fib(5) 会重复计算 fib(3) 两次,fib(2) 三次,递归树里有大量重复节点,复杂度是 O(2^n)。我实测过 fib(40) 都要跑到几秒,n 再大基本卡死。这就是典型的"看起来是分治,实际上是暴力"。

怎么识别?画递归树,看同一个子问题是否在多个分支里重复出现。如果重复,说明应该用动态规划:从递归树底部开始自底向上递推,或者加一个记忆化缓存,把算过的结果存起来。分治和动态规划的边界不是靠背定义,而是靠建模之后检查子问题是否重叠来判定。

现象可能原因排查办法
递归栈溢出递归深度过大改迭代、调大上限、检查枢纽选择
输出部分有序合并逻辑不稳或区间重叠用最小用例手跑一遍
个别元素丢失递归区间漏覆盖打印每次递归的左右边界
运行极慢子问题重复计算画递归树,改用 DP 或记忆化
已排序数组反而最慢快排枢轴选得差随机枢轴或三数取中

5. 分治算法的应用边界与工程延伸

5.1 不适合分治的场景:三种典型的反例

第一种是子问题重叠。斐波那契是典型,区间动态规划里的很多题目也是,这种场景应该用动态规划而不是纯递归分治。

第二种是数据规模太小。分治的递归调用本身有开销,如果数组只有十几个元素,强行分治反而不如插入排序快。很多成熟排序库的工程实现会做一个优化:当递归区间长度小于某个阈值(常见的有 16、32、64)时,直接切换到插入排序,整体性能可以提升不少。这是分治思想在工程实践中的经典补充,面试时提出来会很加分。

第三种是合并信息不足。有些问题分解后,子问题内部的答案无法有效组合成全局答案。比如求数组中所有元素的中位数,直接分治拆开排序再合并,效率未必高;求全局最接近的点对倒是可以高效合并,因为它只需要检查跨界点附近有限区域。这类问题能不能分治,关键看合并步骤是否可控。

5.2 分治、动态规划与贪心:三者到底怎么选

这三个概念初学者经常混淆。我的判断口诀是这样的:先看能否分解;能分解且子问题独立,用分治;能分解但子问题重叠,用动态规划;每一步贪心选择都不会影响后续结果(具备贪心选择性质),用贪心。

算法思想子问题关系典型实现方式代表问题
分治独立自顶向下递归归并排序、快排、最近点对
动态规划重叠自底向上递推 / 记忆化递归最长公共子序列、背包问题
贪心单一路径递推选择局部最优区间调度、哈夫曼编码

举一个最简单直观的例子:斐波那契。用纯分治递归是 O(2^n),用动态规划自底向上是 O(n),用矩阵快速幂甚至能到 O(log n)。同一个问题,选错思想方向,性能天上地下。

5.3 分治思想的工程化:从外部排序到分布式架构

分治不只是面试题,工程上到处都有它的影子。最接地气的是外部排序:当数据量大到内存放不下时,先把大文件切成多个可以放进内存的块,分别排序后写回磁盘,然后用多路归并的方式把有序块合并成一个大文件。这跟归并排序的思路完全同构,经典的外排序实现就是基于这个框架。

再往上走,分布式计算框架里的 Map 和 Reduce 两个阶段,本质上就是分治思想的超大规模实现——Map 阶段把数据切分并并行处理,Reduce 阶段把各部分的结果合并汇总。数据库索引的 B 树 / B+ 树结构,也是通过不断把有序数据分块组织起来,把查找复杂度和树的高度绑定在一起。

我在实际项目里处理日志解析和报表统计时,经常会把大文件按天分片、各自统计、最后合并指标。这就是分治思想最朴素的落地——不依赖任何框架,一个小脚本也能体现出分治的威力。

最后讲一点个人体会。很多人学分治,学完归并排序、快速排序就停了,觉得"算法题而已,工作用不上"。但我在工程里折腾过几轮之后发现,分治思想几乎无处不在:日志文件按时间段切分再合并统计、分布式任务的拆分与汇总、甚至是复杂业务系统的模块化设计,底层都是"拆解 + 解决 + 合并"这个骨架。建议你找几个经典题目,亲手把递归树画出来,把边界条件调对,再回头看这些工程场景,会有完全不同的理解。

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

AI短片创作新范式:从提示词抽卡到3D画布手绘

1. 项目概述&#xff1a;当AI短片创作从“抽卡”走向“手绘”最近在几个创意工作坊里&#xff0c;我反复听到同一个抱怨&#xff1a;“生成一段3秒的AI动画&#xff0c;我调了47条提示词&#xff0c;换了6个模型&#xff0c;试了23种风格关键词&#xff0c;最后出来的还是‘一只…

作者头像 李华
网站建设 2026/10/22 5:51:42

Rust 重写 Agent 运行时:rpi 的架构设计与工程实践

1. 从"终端里的 Pi"说起&#xff1a;rpi 到底想解决什么问题第一次看到"Pi 的 Rust 实现"这个说法&#xff0c;很多人会下意识以为又是一个把某个命令行工具用 Rust 重写一遍的项目。但真正上手 rpi 之后你会发现&#xff0c;它的野心根本不在"重写一…

作者头像 李华
网站建设 2026/10/22 5:51:08

MinGW64静态编译OpenCV 4.10:UCRT+SEH+POSIX生产级集成

简介&#xff1a;本资源为Windows平台下使用MinGW-w64编译完成的OpenCV 4.10完整库文件包&#xff0c;面向C开发者、计算机视觉初学者及需在无MSVC环境&#xff08;如纯GCC工具链&#xff09;中集成OpenCV的嵌入式或跨平台项目实践者。资源解决Windows环境下依赖MSVC运行时的兼…

作者头像 李华
网站建设 2026/10/22 5:51:06

智能科学毕设选题实用方法:从方向拆解到答辩的全流程指南

不知道你有没有过这样的体验&#xff1a;毕设开题前在搜索框里敲下“智能科学毕设题目”&#xff0c;刷了十几页&#xff0c;越看越焦虑。满屏都是“基于的系统”&#xff0c;看起来都差不多&#xff0c;落到自己头上又不知道从哪下手。名字太宽的怕做不完&#xff0c;名字太窄…

作者头像 李华
网站建设 2026/10/22 5:50:17

AI转型需要什么基础?五大方向门槛拆解与自测方法

最近被问得最多的问题&#xff0c;不是“AI怎么学”&#xff0c;而是“想转型AI&#xff0c;我到底缺什么基础”。去网上搜“AI培训”&#xff0c;满屏都是“零基础上岸大厂”“三个月转行AI工程师”&#xff0c;真按这个思路去准备&#xff0c;大概率会被打脸。我做了很多年技…

作者头像 李华
网站建设 2026/10/22 5:49:34

MySQL索引优化:从B+树原理到EXPLAIN慢查询排查

做数据库运维这几年&#xff0c;最常被问到的一个问题就是&#xff1a;为什么同样的 SQL&#xff0c;别人库上几十毫秒&#xff0c;我们库上跑出两秒多&#xff1f;排查到最后&#xff0c;十次里面有八次都落在索引身上。我第一次意识到 MySQL 索引不是“建了就没事了”的时候&…

作者头像 李华