原创

MySQL digest 合并了哪些执行:从平均耗时找回参数差异

慢 SQL 汇总里只有一条 SELECT,应用里却有几百个租户在调用它。这里的一条往往是一种规范化形状:参数字面量被合并,执行次数与时间也累积在同一个摘要下。它能帮你找到工作量集中的查询,却不能直接回答哪个参数最慢。

MySQL 8.4 的 digest 统计适合从全局缩小排查范围。把它当逐请求明细、把平均值当所有参数共同体验,都会在下一步归因时走偏。尤其是多租户、时间跨度差异很大的查询,同一形状内部可能藏着完全不同的数据规模。

两份结果不同,摘要仍然可以相同

先建一个足够小、可以心算结果的实验表。它只验证规范化与分组,不模拟性能倾斜。

CREATE DATABASE IF NOT EXISTS digest43_lab
  CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE digest43_lab;

CREATE TABLE orders_demo (
    id BIGINT UNSIGNED NOT NULL,
    tenant_id BIGINT UNSIGNED NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    created_at DATETIME(6) NOT NULL,
    PRIMARY KEY (id),
    KEY idx_tenant_created (tenant_id,created_at)
) ENGINE=InnoDB;

INSERT INTO orders_demo VALUES
(1,1,10.00,'2025-01-01 00:00:00'),
(2,1,20.00,'2025-01-02 00:00:00'),
(3,2,80.00,'2025-01-03 00:00:00');

SELECT SUM(amount) FROM orders_demo WHERE tenant_id=1;
SELECT SUM(amount) FROM orders_demo WHERE tenant_id=2;

SELECT STATEMENT_DIGEST(
  'SELECT SUM(amount) FROM orders_demo WHERE tenant_id=1'
) = STATEMENT_DIGEST(
  'SELECT SUM(amount) FROM orders_demo WHERE tenant_id=2'
) AS same_digest;

业务结果分别是30.00与80.00,最后的摘要比较应为1。规范化关注语句结构,不保留这里的两个租户数字作为两种形状。STATEMENT_DIGEST_TEXT还可以显示规范化文本,用于检查实际被归为哪一种形式;不要靠手工替换所有数字来仿造摘要算法。

这不意味着任意两条“看起来差不多”的查询都合并。标识符、结构与所用语法仍然参与规范化,默认库也会影响汇总身份。字段相同但连接、排序或过滤结构改变时,应检查实际 DIGEST,而不是只按一段展示文本做字符串归类。

用次数、总耗时和单次分布分别回答问题

Performance Schema 已启用摘要采集时,在观察连接查询目标形状。相关表为空,先核对采集配置和容量,不要把空集解释成这条 SQL 从未执行。

SET @wanted_digest=STATEMENT_DIGEST(
  'SELECT SUM(amount) FROM orders_demo WHERE tenant_id=1'
);

SELECT SCHEMA_NAME,DIGEST,DIGEST_TEXT,COUNT_STAR,
       ROUND(SUM_TIMER_WAIT/1000000000000,6) AS total_seconds,
       ROUND(AVG_TIMER_WAIT/1000000000000,6) AS average_seconds,
       ROUND(MAX_TIMER_WAIT/1000000000000,6) AS maximum_seconds,
       SUM_ROWS_EXAMINED,SUM_ROWS_SENT,SUM_ERRORS,
       FIRST_SEEN,LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME='digest43_lab' AND DIGEST=@wanted_digest;

COUNT_STAR给出该摘要当前累计的执行次数,SUM_TIMER_WAIT给出累计时间,平均值则把许多不同执行压成一个数。高频但单次便宜的查询,可能贡献最大的总工作量;低频却极慢的参数,又可能伤害某一类用户。两项都应看,不能只按平均耗时排一个榜。

这些计时字段采用规范化的皮秒单位,示例统一换成秒。SUM_TIMER_WAIT也不等于CPU占用,里面可能包含等待;跨并发请求累加以后,更不能直接当作一段墙上时间。十个同时执行的请求会贡献十份各自耗时,不需要在单个自然时间段内串行排队才能形成这个总量。

全表看热点时,可以按总时间、次数或扫描行数分别排序,再回到具体摘要确认。某项 SUM_NO_INDEX_USED 很高也不能独自证明“少建了索引”:小表扫描、查询形状和优化器成本选择都需要结合实际语句判断。统计提供入口,不替执行计划作结论。

一条样本 SQL 不代表这个摘要的所有调用

汇总表还有 QUERY_SAMPLE_TEXT、QUERY_SAMPLE_SEEN、QUERY_SAMPLE_TIMER_WAIT,可用于观察保留的代表样本。它不是每次执行一条的完整明细,更不保证始终是最后一次或所有历史中的绝对最慢一次;采样会受执行时间与样本年龄等规则影响。

SELECT QUERY_SAMPLE_TEXT,QUERY_SAMPLE_SEEN,
       ROUND(QUERY_SAMPLE_TIMER_WAIT/1000000000000,6) AS sample_seconds
FROM performance_schema.events_statements_summary_by_digest
WHERE SCHEMA_NAME='digest43_lab' AND DIGEST=@wanted_digest;

本例参数简单,没有敏感资料。真实样本文本可能带业务输入,取证时应限制访问与记录范围,不能把整个摘要榜原样贴到公开报告里。规范化展示隐藏字面量,不代表旁边的采样字段也已经去掉了它们。

如果租户1有很少数据、租户2有大量数据,它们可能共享摘要,却呈现不同扫描规模。要验证这种分布,应从受控请求日志或有限采样中取得代表参数,在隔离环境保留相同表结构、数据分布与绑定类型,分别看执行计划。不能看到样本中的租户编号,就断言这个租户造成了摘要中全部慢时间。

分位数也有自己的口径。表中的 QUANTILE_95 等字段是基于直方图估算的结果,不能把它当成保留了全部原始耗时以后精确排序出的第95百分位。需要核对时,把版本、计时单位、采集窗口与估算方式一起留下来;图表显示三位小数并不会增加底层观测精度。

累计统计不能直接冒充最近五分钟

FIRST_SEEN和LAST_SEEN描述该摘要的观测范围,并不把COUNT_STAR自动限制为最近一个业务窗口。服务器启动、统计重置和表清空等都会影响累计范围;两台机器上的同一个摘要,计数也未必从同一时刻开始。

如果需要两个时间点之间的执行次数,可以对同一实例、同一SCHEMA_NAME与DIGEST保存快照,再比较累计值。中间若发生重置、摘要条目被重新建立或采集状态改变,差值就不能继续按正常增量解释。负差值不应该简单改成零后当作“没有调用”。

平均值尤其不能做两次相减。窗口均值需要用窗口总时间增量除以窗口次数增量;两份累计平均数的差,既不是新增请求平均耗时,也没有稳定业务意义。最大值同样不支持通过相减恢复区间最大值,它是丢失了分布信息的汇总。

摘要容量还有上限。达到相应容量以后,部分新形状可能汇入DIGEST为NULL的汇总行。它不代表一条SQL文本本来就是NULL,也不能和一个普通业务摘要同样归因。观察热点之前先确认条目容量与丢失情况,才能解释“怎么只看见少数形状”。

面对一条慢摘要,下一步通常是保留完整查询形状、选取不同规模的实际参数,再按相同条件核对执行计划。摘要负责把海量调用收拢,参数与分布则负责把问题重新展开;只停留在一个漂亮的平均数上,正好丢掉了最需要解释的那部分差异。

正文到此结束
评论插件初始化中...
Loading...