zoukankan      html  css  js  c++  java
  • 使用WinDbg调试SQL Server查询

    上一篇文章我给你介绍了WinDbg的入门,还有你如何能附加到SQL Server。今天的文章,我们继续往前一步,我会向你展示使用WinDbg调试SQL Server查询需要的步骤。听起来很有意思?我们开始吧!

    假设在你面前有个简单的查询,你想在WinDbg里调试那个特定的查询。听起来很简单,但一旦你开始考虑这个问题,就会碰到很多问题:

    • 在我特定执行的查询上,我如何标识出正确的工作者线程?
    • 在sqlservr.exe里,我应该在哪里设置断点?

    我们来具体讲解下这2个问题。

    标识出正确的工作者线程

    当你在SQL Server里执行一个查询,默认情况下你是不知道查询是在哪个线程上运行的。幸运的是SQL Server在DMV sys.dm_os_threads里提供os_thread_id列来告诉我们。OS线程ID就是用来执行指定查询的。不幸的是你需要从sys.dm_exec_requests直到sys.dm_os_threads连接多个表才可以得到需要的信息。我们来看下面的查询: 

    1 SELECT R.Session_Id, Th.os_thread_id FROM sys.dm_exec_requests R 
    2 JOIN sys.dm_exec_sessions S ON R.session_id = S.session_id 
    3 JOIN sys.dm_os_tasks T ON R.task_address = T.task_address 
    4 JOIN sys.dm_os_workers W ON T.worker_address = W.worker_address 
    5 JOIN sys.dm_os_threads Th ON W.thread_address = Th.thread_address 
    6 WHERE S.is_user_process = 1
    7 GO

    在WinDbg里用CTRL+BREAK中断sqlservr.exe。为了切换到sys.dm_os_thread提供的系统线程ID,你可以用下列WinDbg命令:

    ~~[tid]s

    占位符tid的值就是实际的系统线程ID——16进制值。因此你需要来自sys.dm_os_thread的os_thread_id列值转为16进制值,用刚才提到的命令。当你的系统线程ID是4910时,你应该用下列WinDbg命令切换到正确的线程:

    ~~[132E]s

    当你的查询运行时,对于你的产寻,sys.dm_os_thread只显示系统线程ID。因此就有下一个问题:对于一个执行的查询,我如何获得“正确的”系统线程ID。我这里用一个小技巧:首先我运行一个简单的WAITFOR DELAY命令(例如1分钟),然后再运行实际的查询。如果你用这个方法,你需要保证在1个批处理里提交2个T-SQL查询。不然的话,SQL OS调度会放置WAITFOR语句和实际的查询在2个不同的线程!我们来看实际的代码:

    WAITFOR DELAY '00:01:00'
    
    SELECT
       soh.*,
       d.*
    FROM Sales.SalesOrderHeader soh
    INNER JOIN Sales.SalesOrderDetail d ON soh.SalesOrderID = d.SalesOrderID
    WHERE soh.SalesOrderID = 71832
    AND d.SalesOrderDetailID = 111793
    GO

     在等待期间,你需要进行下列操作:

    1. sys.dm_os_thread为你等待的查询获得在不同会话里系统线程ID
    2. 转化系统线程ID为16进制值
    3. 用CTRL+BREAK中断sqlservr.exe
    4. ~~[tid]命令切换到正确的系统线程ID
    5. 在指定线程上设置断点
    6. 继续sqlservr.exe的运行
    7. 等待直到触发断点

    你要在用WAITFOR DELAY语句引起的延迟时间内完成所有这些操作。如果超过这个时间,这个方法就不可靠了。因此在刚开始的时候,你可以用WAITFOR DELAY设置长一点的延迟时间,直到用这个方法你已经有经验了。

    在sqlservr.exe里设置“好的”断点

    现在你已经从sys.dm_os_thread获得了系统线程ID,而且你用WinDbg挂起了sqlservr.exe的执行。下一步你要在sqlservr.exe里设置断点,这样的话你可以在你的查询里调试并单步执行通过。但什么是好的断点呢?这个看情况:)执行计划里的每个运算符都是用独立的C++类实现的,它里面包含不同的函数。其中一个熟知的函数是GetRow,它返回一行到执行里上迭代器。我的方法如下:在执行计划里,尝试在最左的一个迭代器里设置断点。从我的经验里发现,每个SELECT查询开始于sqlmin!CQueryScan::GetRow的函数调用。

    刚开始在指定类和函数上设置断点应该非常有用。当然你需要花很长时间(当单步执行通过代码时),指导你碰到SQL Server有意思的部分,像B树管理器,或者闩锁/旋转锁的实现。但初次试验时,建议你在特定函数设置断点就可以了。你要确保断点设置在正确的线程上,因为你只想调试你特定查询,没别的!用bm命令在指定线程和符号名上设置断点:

    ~tid bm sqlmin!CQueryScan::GetRow

    但你还要意识到你不必提供系统线程ID。bm命令期望一个从零开始数字线程号。当你用~~[132E]s切换到正确的系统线程时,你会在WinDbg左下角看到线程号:

     

    当WinDbg提示像47的线程号,你可以用下列命令在正确的线程上,在sqlmin!CQueryScan::GetRow函数设置断点:

    ~47 bm sqlmin!CQueryScan::GetRow

    设置断点后,你可以用F5继续sqlservr.exe的运行。几秒后(取决于在WAITFOR语句上设置的延迟)WinDbg应该会在特定的断点中断:

     

    现在好戏才开始:你可以用k命令探索当前的调用堆栈,你可以对汇编代码单步执行通过,看看其他函数是如何调用的。梦想有多远,你的选择就有多远(Your choices are endless, and only limited by your imagination.)。

    小结

    希望这篇文章已经给你以下内容深入的介绍:

    sqlservr.exe里对于指定的查询进行调试时,如何成功的设置断点。

    请继续关注并玩“坏”WinDbg!

    感谢关注!

    参考文章:

    https://www.sqlpassion.at/archive/2014/05/13/debugging-a-sql-server-query-with-windbg/

  • 相关阅读:
    CSU 1333 Funny Car Racing
    FZU 2195 检查站点
    FZU 2193 So Hard
    ZOJ 1655 FZU 1125 Transport Goods
    zoj 2750 Idiomatic Phrases Game
    hdu 1874 畅通工程续
    hdu 2489 Minimal Ratio Tree
    hdu 3398 String
    洛谷 P2158 [SDOI2008]仪仗队 解题报告
    POJ 1958 Strange Towers of Hanoi 解题报告
  • 原文地址:https://www.cnblogs.com/woodytu/p/4665427.html
Copyright © 2011-2022 走看看