試功能詳解:從斷點到Oracle存儲過程排查實戰(zhàn))
聊到ORACLE開發(fā)幾乎繞不過PL/SQL Developer這個工具而我發(fā)現(xiàn)一個挺有意思的現(xiàn)象很多人把PL/SQL Developer當(dāng)“寫代碼跑SQL”的工具用調(diào)試debug功能一年到頭都沒點開過幾次。真到了存儲過程出問題第一反應(yīng)就是往代碼里加dbms_output.put_line跑一遍看輸出不對再改再跑運氣好十分鐘定位運氣不好能折騰一整天。我早年也是這么干的。后來有一次生產(chǎn)環(huán)境一個計算金額的存儲過程算錯了幾百條數(shù)據(jù)我拿日志打了半天看不出端倪最后硬著頭皮用PL/SQL Developer的調(diào)試器一路單步走到那個轉(zhuǎn)換函數(shù)一眼就發(fā)現(xiàn)是除零后沒做空值判斷。那一刻的感受是真香。這篇文章我就專門聊一聊PL/SQL Developer的debug功能從底層邏輯、環(huán)境準(zhǔn)備、核心操作到高級技巧和常見坑全部分享出來。如果你寫Oracle存儲過程、函數(shù)、包或者正在被復(fù)雜PL/SQL日志折磨這篇內(nèi)容值得你花十分鐘讀完之后至少能少走一半彎路。1. 調(diào)試的底層邏輯為什么調(diào)試器能救你于水火1.1 打印日志和斷點調(diào)試差距到底在哪里很多老開發(fā)習(xí)慣用dbms_output.put_line調(diào)試總覺得“能看到輸出不就行了”。但實際上一旦邏輯復(fù)雜起來這個思路會非常痛苦。比如一個存儲過程里有個循環(huán)處理幾十萬行數(shù)據(jù)到第40萬行時計算出一個異常結(jié)果。用put_line的方式要么你打印出幾十萬行日志然后CtrlF搜異常值要么你不停地加條件判斷來縮小范圍。無論哪種都需要反復(fù)修改代碼、重新編譯、重新執(zhí)行每次執(zhí)行還可能帶上真實數(shù)據(jù)的副作用。而調(diào)試器做的事情完全不同它讓你的存儲過程在執(zhí)行到某一行時“暫?!蓖O聛碜屇憧串?dāng)前所有變量的值、當(dāng)前循環(huán)到了第幾行、當(dāng)前游標(biāo)返回了什么數(shù)據(jù)。你可以只停一次看完現(xiàn)場再繼續(xù)往下走。這個體驗有點像是看監(jiān)控視頻找嫌疑人——你不需要把整段錄像一秒一秒拖完只需要在關(guān)鍵幀按暫停鍵放大畫面看清楚。我之前帶過好幾個新人他們有相當(dāng)一部分人連“單步執(zhí)行”是什么意思都不知道全靠打印日志猜邏輯。所以我一直覺得PL/SQL Developer的調(diào)試功能不是花架子它是每個PL/SQL開發(fā)都應(yīng)該掌握的硬技能。1.2 Oracle調(diào)試器的運行機(jī)制會話、DBMS_DEBUG與PL/SQL Developer要理解調(diào)試功能為什么會失效你得先知道它在底層是怎么運作的。Oracle數(shù)據(jù)庫本身提供了一整套面向PL/SQL的調(diào)試接口早期版本是DBMS_DEBUG包后來在12c及更高版本中還引入了DBMS_DEBUG_JDWP等新機(jī)制。PL/SQL Developer并不是自己發(fā)明了一套調(diào)試協(xié)議它只是把Oracle這些底層接口封裝成了圖形化的操作界面。當(dāng)你點擊“Start Debugger”時PL/SQL Developer會與當(dāng)前Oracle會話建立一個調(diào)試連接然后告訴Oracle數(shù)據(jù)庫“我要監(jiān)聽這個會話里的PL/SQL執(zhí)行過程?!睌?shù)據(jù)庫在執(zhí)行到設(shè)置了斷點的行時會把進(jìn)程掛起把控制權(quán)交還給調(diào)試客戶端。此時你在界面上看到的變量值、調(diào)用棧其實都是通過調(diào)試協(xié)議從數(shù)據(jù)庫會話里抓取出來的。明白了這個原理很多調(diào)試問題就都能解釋通了比如為什么沒有調(diào)試權(quán)限就啟動不了調(diào)試為什么對象沒按DEBUG模式編譯斷點就不生效為什么你調(diào)試A過程時想順便走進(jìn)它內(nèi)部調(diào)用的B過程卻發(fā)現(xiàn)B里的斷點沒反應(yīng)——因為數(shù)據(jù)庫壓根沒有把B過程的調(diào)試信息加載進(jìn)來。理解了這層機(jī)制排查起來就有方向了。1.3 調(diào)試的本質(zhì)帶著顯微鏡讀代碼我自己調(diào)試多了之后有個體會調(diào)試器最大的價值是逼著你把代碼一行一行讀懂。很多時候你猜了半天的bug其實代碼邏輯本身早就寫錯了只是你從沒真正執(zhí)行到那個分支看過。舉個例子有個朋友寫過一段分頁邏輯用的是Oracle經(jīng)典的ROWNUM嵌套查詢。結(jié)果某一頁數(shù)據(jù)總是重復(fù)他懷疑是排序字段有重復(fù)值導(dǎo)致分頁不穩(wěn)定但怎么查都查不出來。后來我用調(diào)試器在那個分頁查詢前面的循環(huán)里打斷點一頁一頁往里走才發(fā)現(xiàn)他把頁碼參數(shù)減一之后又做了個絕對值處理第二頁和第三頁實際查詢的范圍是一樣的。這個bug靠打印日志很難發(fā)現(xiàn)但用調(diào)試器看一遍變量變化就一目了然的。所以我常說調(diào)試不是“出了bug才做的事”而是你理解一段陌生代碼最直接的方式。2. 準(zhǔn)備工作做到位調(diào)試才不會半路翻車2.1 權(quán)限不足調(diào)試會直接給你顏色看使用調(diào)試器的第一個前提是權(quán)限。如果你在PL/SQL Developer里點測試、啟動調(diào)試結(jié)果彈出來ORA-01031: insufficient privileges那基本就是權(quán)限沒給夠。常用的調(diào)試權(quán)限主要有這幾個DEBUG CONNECT SESSION允許用戶將會話連接到調(diào)試器DEBUG ANY PROCEDURE允許調(diào)試數(shù)據(jù)庫中任意PL/SQL對象針對某個具體存儲過程的DEBUG權(quán)限GRANT DEBUG ON 某個存儲過程 TO 某個用戶。在實際項目里開發(fā)和測試庫上省事的做法是直接給開發(fā)賬號授予DEBUG CONNECT SESSION和DEBUG ANY PROCEDURE兩個權(quán)限。注意生產(chǎn)環(huán)境我不建議這么干即便是調(diào)試生產(chǎn)庫的問題也建議只給目標(biāo)對象授權(quán)用最小權(quán)限原則。-- 給開發(fā)用戶開放調(diào)試會話權(quán)限 GRANT DEBUG CONNECT SESSION TO dev_user; -- 允許調(diào)試任意PL/SQL對象 GRANT DEBUG ANY PROCEDURE TO dev_user; -- 或者只允許調(diào)試某個具體存儲過程 GRANT DEBUG ON scott.proc_order_calc TO dev_user;如果你在一個項目里換了新賬號卻發(fā)現(xiàn)原來能調(diào)試現(xiàn)在不能了第一件事就查權(quán)限。別去重啟PL/SQL Developer更別去重啟數(shù)據(jù)庫浪費時間。2.2 關(guān)鍵一步用DEBUG模式編譯PL/SQL對象有權(quán)限只是第一步更關(guān)鍵的是你編譯存儲過程的時候必須把調(diào)試信息編進(jìn)去。在PL/SQL Developer里右鍵一個存儲過程可以看到“編譯并啟用調(diào)試”之類的選項菜單里一般也有Add Debug Info/Compile Debug的入口。如果你只是普通的右鍵編譯不會帶調(diào)試信息斷點打上了也白打。SQL層面的寫法是這樣ALTER PROCEDURE proc_order_calc COMPILE DEBUG;如果在PL/SQL Developer里寫代碼一般喜歡直接用菜單操作在左側(cè)對象瀏覽器里找到目標(biāo)過程右鍵 - 編譯并啟用調(diào)試。這樣編譯出來的對象才能被調(diào)試器識別。另外可以設(shè)置讓后續(xù)所有編譯都默認(rèn)帶調(diào)試信息Tools工具- Preferences首選項- Oracle - Compiler把Compile with Debug勾上。不過我建議開發(fā)環(huán)境勾上發(fā)布測試庫和正式庫前一定要重新用非DEBUG模式編譯因為DEBUG編譯會額外占用資源對性能有一點點影響正式環(huán)境沒必要帶調(diào)試信息。2.3 打開測試窗口進(jìn)入調(diào)試世界的入口準(zhǔn)備工作做完后就可以真正開始調(diào)試了。在PL/SQL Developer的左側(cè)對象瀏覽器里展開Procedures或Functions找到你要調(diào)試的存儲過程右鍵它選擇“Test”中文版叫“測試”。這會打開一個測試腳本窗口里面會自動生成一段調(diào)用這個存儲過程的代碼同時窗口下方會顯示輸入?yún)?shù)區(qū)域。有些新手會直接在SQL窗口里寫一個BEGIN ... END;塊來調(diào)用存儲過程然后想調(diào)試這樣是調(diào)不出調(diào)試器的。調(diào)試的入口必須是“Test”這個窗口因為PL/SQL Developer在Test窗口里專門集成了調(diào)試器工具欄其他SQL窗口沒有這個機(jī)制。在測試窗口中你先把入?yún)⑻詈帽热缫粋€日期、一個員工編號然后點擊工具欄上那個像錘子帶著播放符號的按鈕Start Debugger也可以看快捷鍵。點了之后如果一切正常存儲過程就會開始執(zhí)行并停在第一個斷點位置如果沒有斷點會直接跑完整個存儲過程然后把返回值顯示在測試結(jié)果里。所以如果你想一進(jìn)去就逐行看邏輯記得在代碼里先打上斷點或者準(zhǔn)備好逐行步進(jìn)。3. 核心操作從啟動調(diào)試到看懂每一個按鈕3.1 調(diào)試工具欄上的四個關(guān)鍵按鈕搞懂它們就算入門了進(jìn)入調(diào)試狀態(tài)后你會看到一排調(diào)試按鈕很多人一看英文就懵Step Into、Step Over、Step Out、Run……這些都是什么意思我習(xí)慣用電梯來類比。想象你站在一棟樓的某層準(zhǔn)備去訪問一個房間里的另一個人Step Into步入走進(jìn)這個房間進(jìn)入子過程內(nèi)部一行一行看它里面的代碼Step Over單步一步跨過這個房間不進(jìn)去看細(xì)節(jié)直接執(zhí)行完子過程停在當(dāng)前過程的下一行Step Out跳出本來在房間里了不想繼續(xù)看了直接跳出房間回到上一級的走廊Run運行一直往前跑直到遇到下一個斷點為止如果后面沒有斷點就跑完整個過程。這個理解起來其實很簡單但實際項目里很多人的困惑是什么時候用哪個。我的建議是第一次看一段不熟悉的代碼時多用Step Into因為你不知道它調(diào)用的子函數(shù)里有沒有問題如果你只是想快速通過一個已經(jīng)確認(rèn)沒問題的函數(shù)就用Step Over當(dāng)你深入到一個嵌套很深的函數(shù)發(fā)現(xiàn)走偏了就用Step Out趕緊退出來。3.2 斷點怎么打不一定只靠鼠標(biāo)點行號斷點是調(diào)試的“暫停鍵”。在PL/SQL Developer的代碼編輯器里點一下左側(cè)灰色邊框會出現(xiàn)一個紅色圓點這就是斷點。光標(biāo)停在某一行按CtrlB也能快速設(shè)置或取消斷點。但斷點還有一個不容易注意到的細(xì)節(jié)如果斷點打在一行非可執(zhí)行語句上比如注釋行、變量聲明行調(diào)試器不會停在那里而是會跳到下一個可執(zhí)行的位置。所以我建議你盡量把斷點打在真正的執(zhí)行語句上比如賦值、調(diào)用、循環(huán)體內(nèi)部這些位置。調(diào)試過程中可以動態(tài)加斷點。存儲過程已經(jīng)跑起來了跑了一段時間你突然發(fā)現(xiàn)前面某一步想再看一眼不用停掉重來直接在代碼窗口點出斷點然后按Run代碼會停在那個新斷點處。這個技巧在排查長事務(wù)時特別有用。3.3 觀察變量和表達(dá)式調(diào)試的核心價值就在這里斷點暫停下來之后真正干活的是“看變量”。測試窗口下面的位置一般會有兩個標(biāo)簽頁一個是Variables局部變量它會自動列出當(dāng)前代碼作用域內(nèi)的所有變量另一個是Watches監(jiān)視可以自己輸入表達(dá)式。在Variables里你可以看到每個變量的類型、當(dāng)前值。比如一個游標(biāo)變量它能顯示游標(biāo)是否打開、當(dāng)前指向哪一行一個數(shù)值變量你能看到它此刻是0還是1000。這些信息是put_line完全給不了的因為put_line只輸出你讓它輸出的東西而Variables面板會自動把所有變量都列出來。如果你想盯住某個復(fù)雜表達(dá)式比如v_amount * v_rate這個計算結(jié)果可以右鍵某一變量選擇Add to Watches然后在Watches面板里自定義表達(dá)式。調(diào)試過程中這個表達(dá)式會一直保持監(jiān)控狀態(tài)重點關(guān)注某個值的變化時非常好使。3.4 調(diào)試時直接修改變量值測試邊界條件的神器很多初學(xué)者不知道PL/SQL Developer還允許你在調(diào)試過程中“篡改”變量。操作方式很簡單在Variables或Watches面板里雙擊某個變量的值直接輸入新值再回車。下一次代碼執(zhí)行到使用這個變量的地方時用的就是你改過的新值。這個功能太適合測試邊界了。比如有個存儲過程要根據(jù)金額判斷返回不同結(jié)果你想測試金額為0、負(fù)數(shù)、超大值的情況不用反復(fù)修改入?yún)⒃僦匦聠诱{(diào)試只需要在調(diào)試到判斷條件之前把v_amount改成你要測的值就行。我測試數(shù)據(jù)轉(zhuǎn)換時也經(jīng)常這么干比如把一個日期變量臨時改成月底、閏年2月29號幾秒鐘就能驗證一堆邊界場景。需要提醒的是這個修改只在當(dāng)前調(diào)試會話中生效不會改到數(shù)據(jù)庫里的真實數(shù)據(jù)也不會改變源代碼本身所以可以放心大膽試。3.5 調(diào)試過程中查看DBMS_OUTPUT和異常信息有些過程里會寫dbms_output.put_line調(diào)試時這些輸出會在View - DBMS Output面板里實時顯示。但要注意put_line的輸出有緩沖區(qū)限制如果你在循環(huán)里打印了幾萬行緩沖區(qū)滿了之后后面內(nèi)容會被吞掉另外斷點在輸出語句之前暫停時輸出還沒產(chǎn)生所以別一暫停就急著去DBMS Output里找最新一行。更實用的是異常信息的查看。當(dāng)被調(diào)試的過程拋出一個未被捕獲的異常時測試窗口會直接彈出錯誤碼和錯誤信息。比如ORA-06502: numeric or value error你馬上就知道是數(shù)值轉(zhuǎn)換出了問題然后配合斷點往回看是哪個變量在哪個位置變成了非法值。如果想在異常拋出的“那一刻”自動停下來而不是直接跳出調(diào)試可以在PL/SQL Developer的Preferences里找到Debugger相關(guān)選項勾選Break on Exception之類的能力。不同版本叫法略有差異但大致方向都是“在異常發(fā)生時中斷”這個設(shè)置對定位異常非常有用。4. 進(jìn)階操作包體、觸發(fā)器、批量SQL也沒那么可怕4.1 調(diào)試Package包和Package Body包體時的正確姿勢如果你寫的是包而不是獨立存儲過程調(diào)試入口稍微有點不同。在對象瀏覽器里包一般分成Declaration包頭和Body包體兩層。調(diào)試時必須右鍵包體那一層去選Test因為實際代碼邏輯都在包體里只在包頭上操作是進(jìn)不去調(diào)試的。另外編譯時也要對包體執(zhí)行“編譯并啟用調(diào)試”。如果你改的是包體代碼但忘記重新編譯包體斷點同樣不生效。這里有個坑我踩過好多次改完代碼后直接按了CtrlS保存然后啟動調(diào)試但那個保存動作有時只是保存源文件并沒有觸發(fā)重新編譯。必須確認(rèn)編譯窗口里的提示是“Compiled successfully with debug”之類否則繼續(xù)往下查。調(diào)試包里的私有函數(shù)也是同樣的思路在包體代碼行設(shè)置斷點然后從包體入口Test啟動按Step Into就能一步一步走進(jìn)私有函數(shù)。私有函數(shù)在對象瀏覽器里通常不單獨顯示但調(diào)試器沒限制斷點照樣能停。4.2 觸發(fā)器調(diào)試想辦法把DML動作“引”到調(diào)試會話觸發(fā)器調(diào)試和存儲過程不太一樣因為它不是通過Test窗口直接調(diào)用的。說白了觸發(fā)器是DML語句觸發(fā)出來的調(diào)試它的前提是你得先有一個正在被調(diào)試的會話然后在這個會話里執(zhí)行一個能觸發(fā)該觸發(fā)器的INSERT/UPDATE/DELETE。實操上我見過兩種做法。一種是在觸發(fā)器代碼里打上斷點然后啟動某個相關(guān)過程的調(diào)試或者直接啟動一個空測試調(diào)試在SQL窗口里執(zhí)行一條UPDATE看看能不能被調(diào)試器捕獲。另一種是把觸發(fā)器里的核心邏輯先提取成一個存儲過程先把過程調(diào)試好觸發(fā)器里只做一行調(diào)用。第二種方式我強烈推薦因為觸發(fā)器本身很難動態(tài)觀察上下文而把邏輯抽出來之后既方便調(diào)試也方便做單元測試。如果觸發(fā)器斷點一直不命中還要考慮是不是權(quán)限問題當(dāng)前調(diào)試用戶是否有權(quán)限看到那個觸發(fā)器所在的表的DML動作。有時候是另一個會話在執(zhí)行DML而你沒用調(diào)試方式附加到那個用戶自然就斷不到。這里面的本質(zhì)還是我前面說的會話機(jī)制問題——調(diào)試器只能監(jiān)聽它連接的那個數(shù)據(jù)庫會話。4.3 動態(tài)SQL和批量SQL調(diào)試器的能力邊界要清楚有些朋友調(diào)試存儲過程時發(fā)現(xiàn)EXECUTE IMMEDIATE那行能停但停進(jìn)去之后看不到動態(tài)SQL字符串內(nèi)部的執(zhí)行細(xì)節(jié)感覺調(diào)試器“失靈”了。其實這不是失靈而是動態(tài)SQL它本身就是一段運行時才拼接出來的字符串PL/SQL Developer沒有能力去逐行調(diào)試一段它編譯時根本不存在的代碼。同樣FORALL和BULK COLLECT這類批量操作Oracle在底層做了批量綁定和執(zhí)行調(diào)試器可以在語句級別暫停但沒辦法在批量內(nèi)部“一次一次”地走進(jìn)每一行。真要排查批量邏輯的問題我的經(jīng)驗是把批量操作改寫成普通循環(huán)先調(diào)試普通循環(huán)確認(rèn)邏輯再改回批量寫法。同時內(nèi)存里的集合變量可以用調(diào)試器查看集合元素的數(shù)量和內(nèi)容這比打印cover強多了。還有靜態(tài)的SQL語句比如UPDATE、DELETE、SELECT INTO調(diào)試器可以停在SQL語句那一行讓你查看綁定變量的值但它不會帶你進(jìn)入Oracle的SQL引擎內(nèi)部。如果你想分析SQL為什么慢、執(zhí)行計劃為什么不對正路是看執(zhí)行計劃或SQL Trace而不是在PL/SQL調(diào)試器里繞圈。4.4 條件斷點只在特定場景停下來普通斷點每次執(zhí)行到都會停。但如果是循環(huán)十萬次你只想知道當(dāng)v_index等于999時發(fā)生了什么手動按F10按到天亮也按不完。這時候就要用條件斷點。在PL/SQL Developer里右鍵斷點那個紅色圓點選擇Breakpoint Properties斷點屬性然后在Condition里輸入一個布爾表達(dá)式。比如v_index 999這個斷點只有在v_index等于999時才會觸發(fā)其他時候直接跳過。條件里也可以寫復(fù)雜一點比如v_status ERROR AND NVL(v_amount, 0) 0。條件斷點用來排查偶發(fā)性問題特別有效。我處理過一個數(shù)據(jù)校驗過程只有特定機(jī)構(gòu)代碼的數(shù)據(jù)會計算錯誤我在校驗函數(shù)入口設(shè)了一個條件斷點條件寫成v_org_code A001然后一路往后走很快就定位到了那個機(jī)構(gòu)特有的一條分支邏輯。如果沒有條件斷點就只能一遍遍讓代碼在無關(guān)數(shù)據(jù)上暫停。4.5 調(diào)用??焖俑闱濉拔沂菑哪膩硪侥娜ァ碑?dāng)斷點停在某個深層函數(shù)里時很多人會忘記這個函數(shù)是被誰、在哪里調(diào)用的。這時候打開View - Call Stack調(diào)用棧就能看到從最頂層到當(dāng)前行的完整調(diào)用鏈。比如顯示的是proc_main - proc_validate - func_format_date點擊調(diào)用棧里的任何一層代碼窗口會自動跳到對應(yīng)層級的調(diào)用處。這個功能在追查“參數(shù)為什么傳錯”時特別實用。我曾經(jīng)調(diào)試一個包發(fā)現(xiàn)最終計算值時某個字段一直是空順著調(diào)用棧往回翻才發(fā)現(xiàn)是上一層調(diào)用傳參時把兩個參數(shù)的位置調(diào)反了。這類問題如果不看調(diào)用棧光在函數(shù)內(nèi)部看變量完全找不到根源。4.6 調(diào)試多個相關(guān)過程組合起來走一遍主流程你可能會問如果主過程調(diào)AA調(diào)BB又調(diào)C我是不是要分別啟動三次調(diào)試不需要。最簡單的辦法是在最外層入口啟動調(diào)試然后在中間每個關(guān)鍵過程入口處都設(shè)置斷點用Step Into不斷深入或者用Run從一個斷點跳到下一個斷點。這樣整個調(diào)用鏈上的情況都能覆蓋到而且變量值的變化過程是連續(xù)的不會被中斷的會話狀態(tài)打斷。不過要注意Oracle的調(diào)試機(jī)制會有一些限制特別是在“跨會話調(diào)試”和“后臺任務(wù)調(diào)試”比如DBMS_SCHEDULER跑的任務(wù)上。后臺任務(wù)運行在數(shù)據(jù)庫后臺會話中你沒法用PL/SQL Developer直接掛上去。遇到這種情況我基本會復(fù)制任務(wù)邏輯到本地過程里調(diào)試這比跟平臺特性死磕效率高得多。5. 常見問題與排查技巧實錄5.1 點擊Start Debugger沒反應(yīng)或者直接跑完不進(jìn)入調(diào)試這是新手遇到最多的問題原因絕大多數(shù)是對象沒有用DEBUG模式編譯。解決辦法很簡單先停止調(diào)試右鍵對象選擇“編譯并啟用調(diào)試”重新編譯后再點Start Debugger。第二種可能是你壓根沒打斷點而整個過程又沒什么執(zhí)行代碼所以一眨眼就跑完了。就像電影沒設(shè)暫停點你按了播放自然一下就到結(jié)尾了。開局想逐行看的話建議在過程第一行可執(zhí)行語句處打一個斷點再啟動調(diào)試。5.2 報ORA-01031: insufficient privileges怎么辦這就是權(quán)限問題。用前面提到的GRANT語句給當(dāng)前賬號授予DEBUG CONNECT SESSION和DEBUG ANY PROCEDURE。如果調(diào)試的是別人Schema下的對象還需要單獨對該對象授權(quán)GRANT DEBUG ON other_user.proc_calc TO dev_user;另外如果你是通過同義詞訪問的對象調(diào)試時盡量用原始對象名。同義詞有時會讓調(diào)試器找不到底層對象報錯或者斷點不生效。把Object Browser切到原始對象的屬主下右鍵Test會穩(wěn)很多。5.3 斷點是灰色或者打上去沒反應(yīng)斷點灰色一般意味著當(dāng)前斷點所在行的代碼沒有包含調(diào)試信息或者當(dāng)前處于一個不被調(diào)試會話識別的地方。排查思路確認(rèn)當(dāng)前打開的是不是Test窗口對應(yīng)的代碼編輯區(qū)確認(rèn)對象是不是剛剛用DEBUG模式重新編譯過如果你在改的是包體確認(rèn)編譯的是包體而不是包頭確認(rèn)沒有在“只讀”狀態(tài)下打開文件只讀狀態(tài)下斷點沒有意義實在不行Preferences里恢復(fù)Debugger相關(guān)的默認(rèn)設(shè)置。有一個隱藏問題有時你改了代碼但沒有編譯編輯器里顯示的代碼和數(shù)據(jù)庫里實際編譯的對象不一致調(diào)試器還是按數(shù)據(jù)庫里那個舊版本跑。所以打不上斷點時先看代碼右下角或編譯窗口提示的“Last Compiled”時間做到心里有數(shù)。5.4 Oracle 12c/19c版本和客戶端位數(shù)不一致導(dǎo)致的調(diào)試異常有朋友反饋之前用PL/SQL Developer調(diào)試得好好的升級Oracle數(shù)據(jù)庫到19c之后調(diào)試按鈕灰了或者點了就閃退。這個問題很多時候不是PL/SQL Developer壞了而是客戶端的兼容性問題。Oracle 12c之后調(diào)試相關(guān)機(jī)制有升級如果你的PL/SQL Developer版本太老可能跟不上。我的建議是用較新版本比如14/15/16都算穩(wěn)定并且確保PL/SQL Developer的位數(shù)和Oracle客戶端的位數(shù)一致。32位的PL/SQL Developer配64位的Oracle Instant Client容易出現(xiàn)OCI層面的崩潰。保險做法是裝一個完整的32位Oracle Client然后讓PL/SQL Developer指向那個客戶端。還有就是本機(jī)裝了多個Oracle客戶端時PL/SQL Developer連接的OCI庫可能不是你預(yù)期的那一個。在Preferences - Connection里檢查OCI Library的路徑如果不確定就換成明確版本的Instant Client目錄。這類問題表面上看起來是“調(diào)試器壞了”實際是環(huán)境問題。5.5 存儲過程里有COMMIT或ROLLBACK調(diào)試過程中斷這個坑比較隱蔽。某些版本的PL/SQL Developer在調(diào)試過程中執(zhí)行到COMMIT時會話會出現(xiàn)不可預(yù)知的異常比如調(diào)試器失去控制、變量值刷新失敗等。如果你的代碼里確實有顯式的事務(wù)控制語句而你又必須調(diào)試它臨時做法是先注釋掉COMMIT或ROLLBACK等調(diào)完再恢復(fù)。如果邏輯又依賴事務(wù)狀態(tài)那就退一層在最外層包入口調(diào)試別在事務(wù)密集的存儲過程里反復(fù)單步。調(diào)完代碼后一定要記得重新正常編譯別把DEBUG模式的版本發(fā)布上去也不要留著注釋過的事務(wù)語句在生產(chǎn)環(huán)境跑。5.6 快捷鍵速查效率提升最快的路徑調(diào)試這事鼠標(biāo)點來點去和快捷鍵操作的效率差一倍以上。這幾個默認(rèn)快捷鍵建議先背下來功能快捷鍵說明啟動調(diào)試器F9或CtrlF9啟動后會根據(jù)斷點決定停止位置步過/單步F8不進(jìn)入子過程調(diào)用直接執(zhí)行完步入F7進(jìn)入子過程內(nèi)部逐步執(zhí)行步出ShiftF7退出當(dāng)前過程返回調(diào)用點運行到下一個斷點CtrlF10沒有斷點時直接跑完設(shè)置/取消斷點CtrlB光標(biāo)所在行快速切換斷點查看DBMS輸出CtrlD打開DBMS Output面板不同版本、自定義配置下快捷鍵可能有差異但大部分默認(rèn)配置都跑不出這個范圍。建議你以自己版本里菜單或工具欄提示為準(zhǔn)。順手把常用的幾個記牢確實能節(jié)省很多時間。5.7 我平時調(diào)試時的幾個私貨習(xí)慣最后說幾個我自己的實操習(xí)慣不一定都寫在官方文檔里但確實幫我省了不少事。第一寫新存儲過程時我會第一時間用測試窗口跑一個“空跑”把入?yún)⑻詈脝诱{(diào)試如果有異常它會直接停在異常點。這樣能在代碼上線前把三種基本錯誤——語法錯誤、運行時異常、參數(shù)異常都暴露出來。第二調(diào)試函數(shù)時盡量把測試SQL和入?yún)?shù)據(jù)準(zhǔn)備成一個小腳本保留下來。下次回歸測試時直接復(fù)用腳本省得每次都要重新造數(shù)據(jù)。第三遇到“WHEN OTHERS THEN NULL”這種吞異常代碼調(diào)試起來特別痛苦。如果必須調(diào)它我會臨時把異常塊改成“WHEN OTHERS THEN dbms_output.put_line(SQLERRM)”先看到錯誤再繼續(xù)調(diào)完再恢復(fù)原樣。這是沒辦法的辦法但總比在一片黑暗里瞎猜強。第四凡是涉及日期、金額、組織機(jī)構(gòu)的計算邏輯我調(diào)試時必定把邊界值都試一遍比如0、負(fù)數(shù)、月末、年底、拼音大小寫混用的編碼。很多線上事故都是邊界條件沒覆蓋到的結(jié)果。6. 尾聲調(diào)試器不只是修bug的工具我個人這些年的體會是PL/SQL Developer的調(diào)試功能最被低估的地方恰恰不是“排錯”而是“讀代碼”。當(dāng)你接手一個別人寫的幾千行存儲過程與其一行一行干讀不如開個調(diào)試會話把入口參數(shù)填進(jìn)真實環(huán)境的測試數(shù)據(jù)一步步走一遍。代碼的執(zhí)行路徑、變量的變化過程、分支的走向都會變得非常清晰。這比任何文檔都可靠因為代碼不會騙你而注釋和文檔經(jīng)常會。最后再分享一個小技巧調(diào)試的時候不要只盯著“走到哪兒了”還要時不時看看工具欄右側(cè)那個“SQL”區(qū)域或狀態(tài)欄那里往往能顯示當(dāng)前正在執(zhí)行的SQL語句。很多性能問題、鎖表問題就是在調(diào)試過程中順帶發(fā)現(xiàn)的。希望這篇關(guān)于PL/SQL Developer debug功能的梳理能幫你從只會print日志的“腳本俠”進(jìn)化成真正靠調(diào)試器掌控代碼每一個細(xì)節(jié)的PL/SQL開發(fā)者。以后不管碰到多復(fù)雜的存儲過程先別慌開個調(diào)試會話慢慢走一遍答案往往就在你眼前。