Query performance troubleshooting in SQL Server 2008: query_hash and query_plan_hash

本文介绍如何利用SQL Server 2008新增的query_hash和query_plan_hash字段来增强查询性能监控过程。通过对相似逻辑或执行计划的查询进行分组,可以更有效地识别出需要调优的查询,并监测执行计划随时间的变化。
 

Recently I have noticed 2 new columns added to sys.dm_exec_query_stats and sys.dm_exec_requests DMVs in SQL Server 2008: query_hash and query_plan_hash. Those columns can greatly enhance performance monitoring process. In SQL 2005 main query I'm using for query performance monitoring is:

SELECT TOP 10
   qs.execution_count
,
   (
qs.total_physical_reads + qs.total_logical_reads + qs.total_logical_writes) AS [Total IO],
   (
qs.total_physical_reads + qs.total_logical_reads + qs.total_logical_writes) /qs.execution_count AS [Avg IO],
  
SUBSTRING(qt.[text], qs.statement_start_offset/2, (
      
CASE 
          
WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.[text])) *
          
ELSE qs.statement_end_offset 
      
END - qs.statement_start_offset)/
  
) AS query_text,
  
qt.[dbid],
  
qt.objectid,
  
tp.query_plan
FROM 
  
sys.dm_exec_query_stats qs
  
CROSS APPLY sys.dm_exec_sql_text (qs.[sql_handle]) AS qt
  
OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) tp
ORDER BY [Avg IO] DESC
--ORDER BY [Total IO] DESC

It returns top 10 heaviest queries by average IO that exist in cache. I won't go deeper discussing plan cache memory pressure conditions that can force query plan out of cache thus preventing its detection. Let's just say that it works right in 99% of cases which is good enough. Memory pressure can be detected by other queries.

Which queries would be candidates for tuning? First, those with highest total IO. Second, queries with highest average IO that pass certain minimum number of executions criteria (we usually won't tune query, even the heaviest one, that runs once a month in some offline batch). As a side node - you're probably asking, why do I ignore CPU counters like total_worker_time. The reason is simple: in sys.dm_exec_query_stats this counter is unreliable. It shows incorrect numbers in case of parallel execution.

So, Houston,  do we have a problem here? Unfortunately, we do - when application that works with the database doesn't make proper usage of parameterization (and we don't want to force parameterization via database level setting). In such a case we'll see lots of similar queries with 1 or 2 in execution_count and different values of should-be-parameters in query_text. We can miss such queries because every single one is not heavy enough to be of interest or because many of the queries aren't in cache anymore pushed out by new queries - even of the same type. It is especially realistic scenario for 32 bit systems where entire non-data cache is limited to 1GB of space.

What are our options with poorly parameterized queries? We can use CLR user-defined function provided by Itzik Ben-Gan in his book "Inside SQL Server 2005: T-SQL Querying" that uses regular expressions functionality in order to parameterize query text (this function is widely used for Profiler trace analysis). Query text can be passed through the function and used as grouping column. But even if we don't count performance and CPU price of grouping by text column, I know several organizations that just won't let you create your objects in their database.

Taking all the above into account, I was delighted to find out that in SQL 2008 Microsoft added query_hash and query_plan_hash columns to sys.dm_exec_query_stats DMV. query_hash would be the same for queries with similar logic, query_plan_hash would be the same for queries with similar execution plan. And what's the difference? For column with uneven data distribution, execution plan can be different depending on parameter value. If we have the same value in 90% of a table, Optimizer would sure choose scan option. For another value which is responsible for 0.1% of rows Optimizer will prefer index seek with key or RID lookup depending on table's structure. For those two queries we'll see the same query_hash but different query_plan_hash.

So new SQL 2008 version of the query is:

;WITH CTE(TotalExecutions, [Total IO], [Avg IO], StatementTextForExample, plan_handle, QueyHash, QueryPlanHash)
AS
(
  
SELECT TOP 10  
      
SUM(execution_count) AS TotalExecutions,
      
SUM(total_physical_reads + total_logical_reads + total_logical_writes) AS [Total IO],
      
SUM(total_physical_reads + total_logical_reads + total_logical_writes) / SUM(execution_count) AS [Avg IO],
      
MIN(query_text) AS StatementTextForExample,
      
MIN(plan_handle) AS plan_handle,
      
query_hash AS QueryHash,
      
query_plan_hash AS QueryPlanHash
  
FROM 
      
(
          
SELECT 
              
qs.*, 
              
SUBSTRING(qt.[text], qs.statement_start_offset/2, (
                  
CASE 
                      
WHEN qs.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.[text])) *
                      
ELSE qs.statement_end_offset 
                  
END - qs.statement_start_offset)/
              
) AS query_text
          
FROM 
              
sys.dm_exec_query_stats AS qs
              
CROSS APPLY sys.dm_exec_sql_text(qs.[sql_handle]) AS qt
          
WHERE qt.[text] NOT LIKE '%sys.dm_exec_query_stats%'
      
) AS query_stats
  
GROUP BY query_hash, query_plan_hash
  
ORDER BY [Avg IO] DESC
)
SELECT 
  
TotalExecutions, [Total IO], [Avg IO],
  
StatementTextForExample,
  
tp.query_plan AS StatementPlan,
  
QueyHash, QueryPlanHash
FROM
  
CTE
  
OUTER APPLY sys.dm_exec_query_plan(plan_handle) AS tp
ORDER BY [Avg IO] DESC;
--ORDER BY [Total IO] DESC;

Another great usage of new columns is to create repository and monitor execution plan changes over time, i.e. changes in query_plan_hash for the queries with the same query_hash value. In SQL 2005 such monitoring was pretty complicated and required on-the-fly parameterization. In SQL 2008 it looks pretty straightforward.

### 问题分析与解决方案 在使用 winit 库时,可能会遇到与 XInput2 查询版本相关的错误以及 X11 相关的崩溃问题。这种问题通常发生在 Linux 平台上,尤其是在处理输入设备扩展(XInput2)时出现问题。以下是详细的分析和解决方法。 --- #### 错误原因分析 1. **XInput2 版本不兼容** 当调用 `XIQueryVersion` 函数时,如果服务器支持的 XInput 扩展版本低于客户端请求的版本,则会返回错误。这可能导致程序崩溃或抛出异常[^3]。 2. **X11 连接问题** 如果应用程序未能成功连接到 X11 显示服务器,或者在初始化过程中发生其他错误(例如缺少必要的扩展),也可能引发崩溃。 3. **winit 库版本差异** 不同版本的 winit 对 X11 的实现方式有所不同。旧版 winit (如 0.2x.x)可能存在一些未修复的 bug,而新版(如 0.30.0)虽然改进了许多功能,但也引入了一些新的行为变化。 --- #### 解决方案 ##### 1. 检查 XInput2 支持情况 在调用 `XIQueryVersion` 之前,先确认当前 X server 是否支持所需的 XInput2 版本。可以通过以下代码片段完成检查: ```rust use std::ffi::CString; use x11::xlib; fn check_xinput_version(display: *mut xlib::Display) -> Result<(), String> { let major = 2; // 请求的主版本号 let mut minor_returned = 0; // 返回的实际次版本号 unsafe { if !xlib::XQueryExtension( display, CString::new("XInputExtension").unwrap().as_ptr(), &mut 0, // opcode_return &mut 0, // event_return &mut 0, // error_return ) != 0 { match xlib::XIQueryVersion(display, major, &mut minor_returned as *mut _) { 0 => Err(format!( "X server does not support requested XI version ({}, {})", major, minor_returned )), _ => Ok(()), } } else { Err(String::from("XInput extension is not supported by the X server")) } } } ``` 这段代码会在检测不到所需 XInput2 版本时返回错误消息,从而避免后续调用失败导致程序崩溃。 ##### 2. 更新 winit 到最新稳定版本 考虑到不同版本间的 API 差异较大,建议始终使用最新的稳定发布版本。例如,从 0.2x 升级至 0.30.0 后,许多底层实现已被重写以提高可靠性和性能。更新依赖项的方法如下: ```toml [dependencies] winit = "0.30" ``` 同时注意阅读官方迁移指南,了解可能需要调整的地方[^3]。 ##### 3. 处理潜在的线程恐慌 针对提示信息中的 “thread panicked at 'Error while checking for XInput2 query version reply'”,可以在捕获 panic 的地方增加更健壮的错误恢复机制。例如: ```rust std::panic::set_hook(Box::new(|info| { eprintln!("Panic occurred: {:?}", info); })); ``` 这样可以更好地定位问题根源,并防止整个应用因单个线程故障而终止运行。 另外还可以通过自定义错误类型来封装具体的错误场景,便于调试和日志记录: ```rust #[derive(Debug)] pub enum XInitError { UnsupportedXiVersion(u8, u8), // Requested Major.Minor Version MissingXiExtension, } impl From<XInitError> for Box<dyn std::error::Error + Send + Sync> { fn from(err: XInitError) -> Self { match err { XInitError::UnsupportedXiVersion(major, minor) => { format!("Unsupported Xi version {}.{}", major, minor).into() }, XInitError::MissingXiExtension => "X Input Extension missing".into(), } } } ``` ##### 4. 添加补丁修复特定编译问题 如果是在构建阶段遇到了类似 Yocto 编译 gstreamer1.0 的问题,也可以考虑为项目添加针对性的补丁文件。比如下面的例子展示了如何修补 stray backslash 导致的语法错误[^4]: ```patch diff --git a/src/file.c b/src/file.c --- a/src/file.c +++ b/src/file.c @@ -123,7 +123,7 @@ printf("Escaped string:\\n"); - printf("This line contains an invalid escape sequence \\\n"); + printf("This line contains an invalid escape sequence \\.\n"); return 0; } ``` 将此类补丁应用于源码树之后再重新尝试构建流程。 --- ### 总结 通过对 XInput2 查询版本的支持状况进行预检、升级 winit 至最新版本、增强错误处理能力以及必要时运用定制化补丁等方式,能够有效缓解乃至彻底消除上述提到的一系列问题。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值