两份并排的取样记录卡摆在案台上,同一张单据编号对应两个不同的批次标记,旁边是读量与耗时的对照刻度

一、“变慢”和“变了”是两件事,取证顺序不能倒

在洹拓复盘这类问题时,最常见的一步走错,是把“这条语句现在变慢了”和“这条语句的执行计划换了”当成同一件事来查。前者是现象,需要被解释;后者是一条路径层面的结论,需要被证明。坐实“变了”的基本判法并不复杂:看历史统计里同一语句的计划标识是否出现多行。具体动作是把历史计划视图与历史统计视图按语句标识和计划标识关联,再按最早采样时间排序。返回多行,说明计划确实在这个快照窗口内漂移过;只有一行,性能下降大概率与执行路径无关,应当转向输入输出、锁、内存或网络这几条线去查。

只查当前视图,或者只看一次采样,都不足以支撑结论。当前视图会被缓存里的内容干扰,采样时刻恰好落在波动上的风险也不低。所以我们要求的最小证据集是四件,缺一件就不许往“计划变了”上写:同一语句在窗口内出现过的计划标识集合;每个计划标识各自的起止采样时间;每个计划标识对应区间的耗时增量;每个计划标识对应区间的物理读增量。这四件凑齐,才能说出“从哪一个采样点起换了路径”这种带时刻的话。

顺序为什么必须是先证“变了”、再解“变慢”:路径结论一旦没被证明就写进报告,后面所有处置都会围着不存在的原因转——加索引、改统计口径、扩容存储都可能被拿出来做一遍,而真正的原因还在原地。对客户的价值也很实在:带着计划标识集合的取证单是可以复核的,“我们觉得是计划抖动”不是。

二、留存上限决定结论强度,窗口、频率、上限要一起算

两个已知陷阱得先摆在台面上。其一是留存上限:据同一份实测记录,历史计划视图默认对单个语句最多留存一千行计划,高频语句的记录可能被截断。其二是空结果的读法:查询返回为空不等于没有历史计划。这两条都会把“看不见”误读成“没发生”,而误读的代价是不对等的一侧——本来该被排除的路径变化被当成已经排除。

截断风险怎么影响结论,取决于三个量的配合:快照窗口长度、语句的调用频率、单语句的留存上限。低频语句在上限的余量里通常能留下完整的计划序列,“只有一行”这个结论就相当硬。高频语句相反,同一窗口内被采到的次数很容易把上限吃满,这时看到几行并不代表只有几行,只能说明可见的部分里只有这几行。判断自己的结论有多硬,可以按一条朴素的规则:先用调用频率乘窗口长度估一下样本量,估出的量一旦接近上限,本次结论的措辞就要从“没有漂移”降级成“可见范围内未见漂移”。

证据不足时该降到什么层去做,也是提前要定的事。两条路:一是把保留窗扩长,让它至少覆盖性能回归从出现到被确认所需的那段区间,而不是沿用平台默认的短窗;二是按快照把语句与计划的关系导出留档,把结论的证据基础从“依赖内置留存”改成“依赖自己的归档”。这里要写清谁承担成本:扩窗占采样与存储,按快照导出占人工与归档位,这两项都该由性能问题的责任方与平台方一起定,而不是让值班的人临时决定。我们的做法是把这段判据写进交付文档:凡是要用“计划未变”来排除路径原因的结论,必须注明当时用的窗口长度与是否存在截断风险。

三、变了还不算完,要比的是差异是否致命

计划标识换了本身不说明问题严重程度,必须比对差异是不是致命的。要盯的两类形态很具体:索引扫描退化成全表扫描;嵌套循环被换成散列连接,进而带来大量临时段写入。比对的载体是带绑定值窥探与备注的展开格式,重点落在访问谓词与过滤谓词是否还停在索引列上。

我们把这一层做成一张对照表,两侧各填一列,缺行就不许下结论。

比对项 旧计划一侧 新计划一侧 判读
访问方式 索引扫描 全表扫描 对应读放大,物理读增量优先看这里
连接方式 嵌套循环 散列连接 一旦伴随临时段写入暴涨,指向溢写
被过滤的列 落在索引列 仅作为过滤谓词 谓词从访问侧退到过滤侧,代价按行数放大
预估与实际行数 接近 差距悬殊 基数偏差是换路径的主要嫌疑
绑定值窥探 有 无 缺这一行,整张表不能出“没变坏”的结论

这张表里最容易被跳过的是绑定值。若展开结果里没有绑定值,就看不到真实取值,很可能把一个本来无效或本来有效的谓词判反,后面的结论全跟着歪。而提示无行也不能直接读成计划不存在,这种情况常见于并行计划或递归调用未被完整记录,属于取证的缺口而不是路径的证据。

基数偏差要单独给一条判读线:预估行数与实际返回行数相差一个数量级及以上,不管是被估大还是被估小,都应当作关键异常来看。同一份记录里给过两组量级举例,一处是预估一百行、实际返回十二万行,一处是某个快照的物理读增量从五百跳到十二万。这两个数只在原文口径里作举例,本篇不把它们当通用阈值,也不作为任何判断依据;要用的时候,用比值而不是绝对值去判断,并把当次的数据量级一起写进结论。

四、耗时增量与物理读增量放同一张图上做分诊

计划突变本身不写磁盘,但它会引发一连串输入输出行为:全表扫描触发大量单块顺序读,散列连接溢出到临时表空间导致物理写暴涨。所以交叉验证的走法是反过来的:先在快照序列上定位物理读增量突增的那个时段,再查同一快照范围内该语句是否出现新的计划标识、且旧计划同时消失。两侧时刻能对上,路径结论才算坐实;对不上,就不要把它写成原因。

把耗时增量与物理读增量这两个量按是否突增切成四格,每格对应不同的下一步。

  1. 两项同向突增:路径变化与负载变化叠加,继续按第三节的对照表核谓词落点与基数偏差,同时确认是否有溢写临时表空间。
  2. 耗时没有明显上涨、物理读增量翻倍:基本可断定是读取路径恶化而非语句逻辑变重,转存储与设备侧核对,不要再在语句本身找原因。
  3. 耗时上涨、物理读没涨:问题不在读路径上,转锁等待与内存争用这两条线;若等待集中在跨机往返上,则转网络。
  4. 两项都不涨但使用者仍反馈慢:不在本语句,去看排队、连接层与上下游服务,本篇不重复那一层的判据。

第二格这一条分诊规则值得单拎出来,因为它纠正的是一个方向性误判:单看耗时这一条,“耗时没涨”很容易被读成“逻辑没变”,于是多出来的那些物理读就被顺手解释成业务量上涨。可如果同期计划标识也换过、访问方式退成了全表扫描,那么真正变的是读法而不是量。把耗时当成唯一标尺,就会把路径恶化记在数据增长头上,处置自然也做歪。

五、集群必须按实例分列,总量达标不等于每台都健康

多实例环境下按实例分开查是硬要求,否则聚合之后的物理写会掩盖单节点热点。这一层的失真机制很直白:聚合值把节点之间的差别平均掉了,整个集群看着只是略高,热点实例上那一条语句可能已经把临时表空间的占用推得很紧;而业务感知是整体的延迟,看不出这份延迟是从哪台机器来的。

按实例分列以后,有两项要额外看:热点实例的会话分布,看并发是不是被压在同一台机器上;临时表空间占用,看溢写是不是集中在个别节点。我们建议的报告形式是语句乘以实例的一张矩阵,每个格子里放计划标识数、耗时增量、物理读增量三个量,而不是一行汇总。结论的措辞也跟着改:不能写“该语句计划稳定”,要写“该语句在各实例上的计划标识数分别是多少”。

还有一个采样时刻的问题:不同实例的快照边界未必严格对齐,跨实例比较增量时要先确认两个区间是否一致,否则一侧比另一侧多覆盖了一段高峰,差异会被误读成节点不均衡。这也是取证模板里要预先填好的一栏。

六、假漂移要按固定顺序排除,并把规则写进值班手册

有一类漂移不是应用行为造成的。同一语句在非业务时段集中出现多个计划标识,大概率是后台任务驱动的结果。要过滤掉的语句有三类:统计信息收集、物化视图刷新、备份工具自身发出的语句。这三类的身份是任务本身,不是受害者,把它们和应用语句混在一起统计,会把运维动作的峰值读成业务回归。

标准顺序是两步,前后不能换。第一步先剔除运维自身语句,把统计口径收回到应用侧;第二步再把调度任务的运行开始时间与快照区间对齐,检查计划标识的出现时刻是否落在某个后台任务的运行段内。顺序之所以要这样排,是因为先做时间对齐会把那些本就该被剔除的语句拉进对照区间,越对齐越像有话要说;剔除完再对齐,剩下的漂移才值得当作异常继续往下走。

顺带一提,统计信息刷新之后计划合理地换了一版,这是运维的预期结果而不是事故。值班手册要把这一类写成可核对的形式:判定动作是什么、剔除清单里有哪几类语句、后台任务的登记归谁。责任切分上,我们倾向于登记由调度任务的归属方负责,谁排的班谁说明运行窗口和语句特征;手册条目由数据库平台侧负责,把判据、剔除清单、结论措辞落成模板;性能问题的当事人只负责按模板出证,不负责临时发明判据。这条规则的价值在于,把“这段漂移是假警报”的判断从一次经验变成一个可重复的动作,也免得每个值班的人都要重新想一遍该先剔什么。