2. Query Store架构与内部机制
Query Store 并不是一个“打开就自动优化”的开关,它本质上是一套运行在 SQL Server 引擎内部的持久化查询性能仓库。理解它的架构,才能解释为什么有时能看到历史计划、有时强制计划却不生效、有时数据会突然“消失”。本节从存储结构、采集流程、计划强制和后台任务四个层面拆解其内部机制。
一、存储结构:内存与磁盘的双层设计
Query Store 的数据并非直接写入用户数据库的普通表,而是由引擎以内部结构管理,逻辑上分为三层:
- 内存中的运行时统计(Runtime Stats):每个查询计划在内存中维护执行次数、CPU 时间、逻辑读、等待时间等增量计数器,采集开销极低。
- 内存中的计划与查询文本缓存:保存 query_hash、query_plan_hash、计划 XML 及编译时的 SET 选项、兼容级别等上下文。
- 磁盘上的持久化存储:由后台任务按间隔将内存数据刷入数据库内的 Query Store 内部表,重启后仍可恢复。
磁盘部分主要对应三组内部对象:sys.query_store_query(查询文本与上下文)、sys.query_store_plan(计划及其编译统计)、sys.query_store_runtime_stats(按时间间隔聚合的运行时指标)。它们通过 query_id、plan_id 关联,形成“查询—计划—运行时”的三级关系。理解这一点,就能明白为什么同一个 query_id 下可能挂多个 plan_id,而每个 plan_id 又对应多个时间区间的 runtime_stats。
二、数据采集流程:从执行到落盘
采集流程可以概括为“执行时累加、间隔刷写、按策略清理”。当一条语句编译并执行时,引擎在内存中定位或创建对应的 query_id 与 plan_id,然后把本次执行的 CPU、逻辑读、持续时间、等待类型等增量累加到该 plan_id 的运行时计数器上。这个过程发生在语句执行路径中,因此 Query Store 的开启会带来一定的写入开销,但远小于逐条记录执行明细。
内存数据不会立即落盘,而是由后台任务按 INTERVAL_LENGTH_MINUTES(默认 60 分钟)将聚合结果写入 sys.query_store_runtime_stats。这意味着:
- 刚执行的语句可能还看不到最新统计,需要等待刷写或手动触发;
- 时间间隔决定了统计粒度,间隔越短,历史数据点越多,磁盘增长越快;
- 查询
sys.query_store_runtime_stats时看到的是“已落盘”的聚合值,而非实时值。
清理策略由 STALE_QUERY_THRESHOLD_DAYS(默认 30 天)和 MAX_STORAGE_SIZE_MB(默认 100 MB)共同控制。当达到容量上限时,Query Store 会优先清理最旧、最不活跃的数据;若清理速度跟不上写入速度,会进入只读状态并报错。因此生产环境通常需要根据负载调整这两个参数。
三、计划强制机制:让优化器“听话”
计划强制是 Query Store 最核心的治理能力。当某个查询因参数嗅探、统计信息变化或编译抖动而选择了劣质计划时,DBA 可以将其历史中的某个“好计划”标记为强制计划。此后该查询再次编译时,优化器会优先尝试使用被强制的计划。
强制并非“无条件替换”。引擎会先校验强制计划是否仍然可用,例如:计划引用的对象是否存在、SET 选项是否兼容、计划是否因架构变更而失效。若校验通过,则直接复用;若失败,则回退到正常编译,并在 sys.query_store_plan 的 last_force_failure_reason 中记录原因。常见的失败原因包括:
- Plan 已被清除:强制计划对应的 plan_id 因清理策略被删除;
- 对象或架构变更:表结构、索引或统计信息变化导致计划不再适用;
- SET 选项不匹配:当前会话的 SET 选项与计划编译时不一致;
- 计划本身不可强制:如包含某些不支持的构造。
因此,强制计划后必须持续观察 is_forced_plan 与失败原因,而不是“一强了之”。
四、后台任务:谁在维护 Query Store
Query Store 依赖多个后台任务协同工作,主要包括:
- 刷写任务:按间隔将内存运行时统计写入磁盘;
- 清理任务:按保留策略删除过期数据,控制磁盘占用;
- 计划强制校验任务:在编译时校验强制计划可用性,并记录失败原因;
- 统计聚合任务:将原始运行时数据按时间间隔聚合,供报表查询。
这些任务由 SQL Server 内部调度,DBA 无法直接控制其执行频率,但可以通过配置参数间接影响其行为。例如,缩短 INTERVAL_LENGTH_MINUTES 会增加刷写频率和数据点数量;增大 MAX_STORAGE_SIZE_MB 会延缓清理触发。理解后台任务的存在,有助于解释“为什么刚开启 Query Store 时数据很少”“为什么历史数据会突然减少”等现象。
sys.database_query_store_options 查看 actual_state_desc(READ_WRITE / READ_ONLY / ERROR)、readonly_reason 和当前存储使用量,再结合 sys.query_store_plan 的强制失败原因定位问题,而不是直接怀疑查询本身。
综上,Query Store 的架构可以概括为:内存累加、间隔刷写、按策略清理、编译时强制、后台任务维护。掌握这五条主线,就能在后续的性能治理中准确判断数据来源、强制效果和异常状态。