標籤:bug 管理器 min 文章 use 處理 調試 header .sql
上一篇文章我給你介紹了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 = 17 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 sohINNER JOIN Sales.SalesOrderDetail d ON soh.SalesOrderID = d.SalesOrderIDWHERE soh.SalesOrderID = 71832AND d.SalesOrderDetailID = 111793GO
在等待期間,你需要進行下列操作:
- 從sys.dm_os_thread為你等待的查詢獲得在不同會話裡系統線程ID
- 轉化系統線程ID為16進位值
- 用CTRL+BREAK中斷sqlservr.exe
- 用~~[tid]命令切換到正確的系統線程ID
- 在指定線程上設定斷點
- 繼續sqlservr.exe的運行
- 等待直到觸發斷點
你要在用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/
如何設斷點????-----使用WinDbg調試SQL Server查詢