天天看點

使用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調試SQL Server查詢

當WinDbg提示像47的線程号,你可以用下列指令在正确的線程上,在sqlmin!CQueryScan::GetRow函數設定斷點:

~47 bm sqlmin!CQueryScan::GetRow

設定斷點後,你可以用F5繼續sqlservr.exe的運作。幾秒後(取決于在WAITFOR語句上設定的延遲)WinDbg應該會在特定的斷點中斷:

使用WinDbg調試SQL Server查詢

現在好戲才開始:你可以用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/

注:此文章為

WoodyTu

學習MS SQL技術,收集整理相關文檔撰寫,歡迎轉載,請在文章頁面明顯位置給出此文連結!

若您覺得這篇文章還不錯請點選下右下角的推薦,有了您的支援才能激發作者更大的寫作熱情,非常感謝!