ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

PL/SQL Developer调试功能详解:从断点到Oracle存储过程排查实战

PL/SQL Developer调试功能详解:从断点到Oracle存储过程排查实战 聊到ORACLE开发几乎绕不过PL/SQL Developer这个工具而我发现一个挺有意思的现象很多人把PL/SQL Developer当“写代码跑SQL”的工具用调试debug功能一年到头都没点开过几次。真到了存储过程出问题第一反应就是往代码里加dbms_output.put_line跑一遍看输出不对再改再跑运气好十分钟定位运气不好能折腾一整天。我早年也是这么干的。后来有一次生产环境一个计算金额的存储过程算错了几百条数据我拿日志打了半天看不出端倪最后硬着头皮用PL/SQL Developer的调试器一路单步走到那个转换函数一眼就发现是除零后没做空值判断。那一刻的感受是真香。这篇文章我就专门聊一聊PL/SQL Developer的debug功能从底层逻辑、环境准备、核心操作到高级技巧和常见坑全部分享出来。如果你写Oracle存储过程、函数、包或者正在被复杂PL/SQL日志折磨这篇内容值得你花十分钟读完之后至少能少走一半弯路。1. 调试的底层逻辑为什么调试器能救你于水火1.1 打印日志和断点调试差距到底在哪里很多老开发习惯用dbms_output.put_line调试总觉得“能看到输出不就行了”。但实际上一旦逻辑复杂起来这个思路会非常痛苦。比如一个存储过程里有个循环处理几十万行数据到第40万行时计算出一个异常结果。用put_line的方式要么你打印出几十万行日志然后CtrlF搜异常值要么你不停地加条件判断来缩小范围。无论哪种都需要反复修改代码、重新编译、重新执行每次执行还可能带上真实数据的副作用。而调试器做的事情完全不同它让你的存储过程在执行到某一行时“暂停”停下来让你看当前所有变量的值、当前循环到了第几行、当前游标返回了什么数据。你可以只停一次看完现场再继续往下走。这个体验有点像是看监控视频找嫌疑人——你不需要把整段录像一秒一秒拖完只需要在关键帧按暂停键放大画面看清楚。我之前带过好几个新人他们有相当一部分人连“单步执行”是什么意思都不知道全靠打印日志猜逻辑。所以我一直觉得PL/SQL Developer的调试功能不是花架子它是每个PL/SQL开发都应该掌握的硬技能。1.2 Oracle调试器的运行机制会话、DBMS_DEBUG与PL/SQL Developer要理解调试功能为什么会失效你得先知道它在底层是怎么运作的。Oracle数据库本身提供了一整套面向PL/SQL的调试接口早期版本是DBMS_DEBUG包后来在12c及更高版本中还引入了DBMS_DEBUG_JDWP等新机制。PL/SQL Developer并不是自己发明了一套调试协议它只是把Oracle这些底层接口封装成了图形化的操作界面。当你点击“Start Debugger”时PL/SQL Developer会与当前Oracle会话建立一个调试连接然后告诉Oracle数据库“我要监听这个会话里的PL/SQL执行过程。”数据库在执行到设置了断点的行时会把进程挂起把控制权交还给调试客户端。此时你在界面上看到的变量值、调用栈其实都是通过调试协议从数据库会话里抓取出来的。明白了这个原理很多调试问题就都能解释通了比如为什么没有调试权限就启动不了调试为什么对象没按DEBUG模式编译断点就不生效为什么你调试A过程时想顺便走进它内部调用的B过程却发现B里的断点没反应——因为数据库压根没有把B过程的调试信息加载进来。理解了这层机制排查起来就有方向了。1.3 调试的本质带着显微镜读代码我自己调试多了之后有个体会调试器最大的价值是逼着你把代码一行一行读懂。很多时候你猜了半天的bug其实代码逻辑本身早就写错了只是你从没真正执行到那个分支看过。举个例子有个朋友写过一段分页逻辑用的是Oracle经典的ROWNUM嵌套查询。结果某一页数据总是重复他怀疑是排序字段有重复值导致分页不稳定但怎么查都查不出来。后来我用调试器在那个分页查询前面的循环里打断点一页一页往里走才发现他把页码参数减一之后又做了个绝对值处理第二页和第三页实际查询的范围是一样的。这个bug靠打印日志很难发现但用调试器看一遍变量变化就一目了然的。所以我常说调试不是“出了bug才做的事”而是你理解一段陌生代码最直接的方式。2. 准备工作做到位调试才不会半路翻车2.1 权限不足调试会直接给你颜色看使用调试器的第一个前提是权限。如果你在PL/SQL Developer里点测试、启动调试结果弹出来ORA-01031: insufficient privileges那基本就是权限没给够。常用的调试权限主要有这几个DEBUG CONNECT SESSION允许用户将会话连接到调试器DEBUG ANY PROCEDURE允许调试数据库中任意PL/SQL对象针对某个具体存储过程的DEBUG权限GRANT DEBUG ON 某个存储过程 TO 某个用户。在实际项目里开发和测试库上省事的做法是直接给开发账号授予DEBUG CONNECT SESSION和DEBUG ANY PROCEDURE两个权限。注意生产环境我不建议这么干即便是调试生产库的问题也建议只给目标对象授权用最小权限原则。-- 给开发用户开放调试会话权限 GRANT DEBUG CONNECT SESSION TO dev_user; -- 允许调试任意PL/SQL对象 GRANT DEBUG ANY PROCEDURE TO dev_user; -- 或者只允许调试某个具体存储过程 GRANT DEBUG ON scott.proc_order_calc TO dev_user;如果你在一个项目里换了新账号却发现原来能调试现在不能了第一件事就查权限。别去重启PL/SQL Developer更别去重启数据库浪费时间。2.2 关键一步用DEBUG模式编译PL/SQL对象有权限只是第一步更关键的是你编译存储过程的时候必须把调试信息编进去。在PL/SQL Developer里右键一个存储过程可以看到“编译并启用调试”之类的选项菜单里一般也有Add Debug Info/Compile Debug的入口。如果你只是普通的右键编译不会带调试信息断点打上了也白打。SQL层面的写法是这样ALTER PROCEDURE proc_order_calc COMPILE DEBUG;如果在PL/SQL Developer里写代码一般喜欢直接用菜单操作在左侧对象浏览器里找到目标过程右键 - 编译并启用调试。这样编译出来的对象才能被调试器识别。另外可以设置让后续所有编译都默认带调试信息Tools工具- Preferences首选项- Oracle - Compiler把Compile with Debug勾上。不过我建议开发环境勾上发布测试库和正式库前一定要重新用非DEBUG模式编译因为DEBUG编译会额外占用资源对性能有一点点影响正式环境没必要带调试信息。2.3 打开测试窗口进入调试世界的入口准备工作做完后就可以真正开始调试了。在PL/SQL Developer的左侧对象浏览器里展开Procedures或Functions找到你要调试的存储过程右键它选择“Test”中文版叫“测试”。这会打开一个测试脚本窗口里面会自动生成一段调用这个存储过程的代码同时窗口下方会显示输入参数区域。有些新手会直接在SQL窗口里写一个BEGIN ... END;块来调用存储过程然后想调试这样是调不出调试器的。调试的入口必须是“Test”这个窗口因为PL/SQL Developer在Test窗口里专门集成了调试器工具栏其他SQL窗口没有这个机制。在测试窗口中你先把入参填好比如一个日期、一个员工编号然后点击工具栏上那个像锤子带着播放符号的按钮Start Debugger也可以看快捷键。点了之后如果一切正常存储过程就会开始执行并停在第一个断点位置如果没有断点会直接跑完整个存储过程然后把返回值显示在测试结果里。所以如果你想一进去就逐行看逻辑记得在代码里先打上断点或者准备好逐行步进。3. 核心操作从启动调试到看懂每一个按钮3.1 调试工具栏上的四个关键按钮搞懂它们就算入门了进入调试状态后你会看到一排调试按钮很多人一看英文就懵Step Into、Step Over、Step Out、Run……这些都是什么意思我习惯用电梯来类比。想象你站在一栋楼的某层准备去访问一个房间里的另一个人Step Into步入走进这个房间进入子过程内部一行一行看它里面的代码Step Over单步一步跨过这个房间不进去看细节直接执行完子过程停在当前过程的下一行Step Out跳出本来在房间里了不想继续看了直接跳出房间回到上一级的走廊Run运行一直往前跑直到遇到下一个断点为止如果后面没有断点就跑完整个过程。这个理解起来其实很简单但实际项目里很多人的困惑是什么时候用哪个。我的建议是第一次看一段不熟悉的代码时多用Step Into因为你不知道它调用的子函数里有没有问题如果你只是想快速通过一个已经确认没问题的函数就用Step Over当你深入到一个嵌套很深的函数发现走偏了就用Step Out赶紧退出来。3.2 断点怎么打不一定只靠鼠标点行号断点是调试的“暂停键”。在PL/SQL Developer的代码编辑器里点一下左侧灰色边框会出现一个红色圆点这就是断点。光标停在某一行按CtrlB也能快速设置或取消断点。但断点还有一个不容易注意到的细节如果断点打在一行非可执行语句上比如注释行、变量声明行调试器不会停在那里而是会跳到下一个可执行的位置。所以我建议你尽量把断点打在真正的执行语句上比如赋值、调用、循环体内部这些位置。调试过程中可以动态加断点。存储过程已经跑起来了跑了一段时间你突然发现前面某一步想再看一眼不用停掉重来直接在代码窗口点出断点然后按Run代码会停在那个新断点处。这个技巧在排查长事务时特别有用。3.3 观察变量和表达式调试的核心价值就在这里断点暂停下来之后真正干活的是“看变量”。测试窗口下面的位置一般会有两个标签页一个是Variables局部变量它会自动列出当前代码作用域内的所有变量另一个是Watches监视可以自己输入表达式。在Variables里你可以看到每个变量的类型、当前值。比如一个游标变量它能显示游标是否打开、当前指向哪一行一个数值变量你能看到它此刻是0还是1000。这些信息是put_line完全给不了的因为put_line只输出你让它输出的东西而Variables面板会自动把所有变量都列出来。如果你想盯住某个复杂表达式比如v_amount * v_rate这个计算结果可以右键某一变量选择Add to Watches然后在Watches面板里自定义表达式。调试过程中这个表达式会一直保持监控状态重点关注某个值的变化时非常好使。3.4 调试时直接修改变量值测试边界条件的神器很多初学者不知道PL/SQL Developer还允许你在调试过程中“篡改”变量。操作方式很简单在Variables或Watches面板里双击某个变量的值直接输入新值再回车。下一次代码执行到使用这个变量的地方时用的就是你改过的新值。这个功能太适合测试边界了。比如有个存储过程要根据金额判断返回不同结果你想测试金额为0、负数、超大值的情况不用反复修改入参再重新启动调试只需要在调试到判断条件之前把v_amount改成你要测的值就行。我测试数据转换时也经常这么干比如把一个日期变量临时改成月底、闰年2月29号几秒钟就能验证一堆边界场景。需要提醒的是这个修改只在当前调试会话中生效不会改到数据库里的真实数据也不会改变源代码本身所以可以放心大胆试。3.5 调试过程中查看DBMS_OUTPUT和异常信息有些过程里会写dbms_output.put_line调试时这些输出会在View - DBMS Output面板里实时显示。但要注意put_line的输出有缓冲区限制如果你在循环里打印了几万行缓冲区满了之后后面内容会被吞掉另外断点在输出语句之前暂停时输出还没产生所以别一暂停就急着去DBMS Output里找最新一行。更实用的是异常信息的查看。当被调试的过程抛出一个未被捕获的异常时测试窗口会直接弹出错误码和错误信息。比如ORA-06502: numeric or value error你马上就知道是数值转换出了问题然后配合断点往回看是哪个变量在哪个位置变成了非法值。如果想在异常抛出的“那一刻”自动停下来而不是直接跳出调试可以在PL/SQL Developer的Preferences里找到Debugger相关选项勾选Break on Exception之类的能力。不同版本叫法略有差异但大致方向都是“在异常发生时中断”这个设置对定位异常非常有用。4. 进阶操作包体、触发器、批量SQL也没那么可怕4.1 调试Package包和Package Body包体时的正确姿势如果你写的是包而不是独立存储过程调试入口稍微有点不同。在对象浏览器里包一般分成Declaration包头和Body包体两层。调试时必须右键包体那一层去选Test因为实际代码逻辑都在包体里只在包头上操作是进不去调试的。另外编译时也要对包体执行“编译并启用调试”。如果你改的是包体代码但忘记重新编译包体断点同样不生效。这里有个坑我踩过好多次改完代码后直接按了CtrlS保存然后启动调试但那个保存动作有时只是保存源文件并没有触发重新编译。必须确认编译窗口里的提示是“Compiled successfully with debug”之类否则继续往下查。调试包里的私有函数也是同样的思路在包体代码行设置断点然后从包体入口Test启动按Step Into就能一步一步走进私有函数。私有函数在对象浏览器里通常不单独显示但调试器没限制断点照样能停。4.2 触发器调试想办法把DML动作“引”到调试会话触发器调试和存储过程不太一样因为它不是通过Test窗口直接调用的。说白了触发器是DML语句触发出来的调试它的前提是你得先有一个正在被调试的会话然后在这个会话里执行一个能触发该触发器的INSERT/UPDATE/DELETE。实操上我见过两种做法。一种是在触发器代码里打上断点然后启动某个相关过程的调试或者直接启动一个空测试调试在SQL窗口里执行一条UPDATE看看能不能被调试器捕获。另一种是把触发器里的核心逻辑先提取成一个存储过程先把过程调试好触发器里只做一行调用。第二种方式我强烈推荐因为触发器本身很难动态观察上下文而把逻辑抽出来之后既方便调试也方便做单元测试。如果触发器断点一直不命中还要考虑是不是权限问题当前调试用户是否有权限看到那个触发器所在的表的DML动作。有时候是另一个会话在执行DML而你没用调试方式附加到那个用户自然就断不到。这里面的本质还是我前面说的会话机制问题——调试器只能监听它连接的那个数据库会话。4.3 动态SQL和批量SQL调试器的能力边界要清楚有些朋友调试存储过程时发现EXECUTE IMMEDIATE那行能停但停进去之后看不到动态SQL字符串内部的执行细节感觉调试器“失灵”了。其实这不是失灵而是动态SQL它本身就是一段运行时才拼接出来的字符串PL/SQL Developer没有能力去逐行调试一段它编译时根本不存在的代码。同样FORALL和BULK COLLECT这类批量操作Oracle在底层做了批量绑定和执行调试器可以在语句级别暂停但没办法在批量内部“一次一次”地走进每一行。真要排查批量逻辑的问题我的经验是把批量操作改写成普通循环先调试普通循环确认逻辑再改回批量写法。同时内存里的集合变量可以用调试器查看集合元素的数量和内容这比打印cover强多了。还有静态的SQL语句比如UPDATE、DELETE、SELECT INTO调试器可以停在SQL语句那一行让你查看绑定变量的值但它不会带你进入Oracle的SQL引擎内部。如果你想分析SQL为什么慢、执行计划为什么不对正路是看执行计划或SQL Trace而不是在PL/SQL调试器里绕圈。4.4 条件断点只在特定场景停下来普通断点每次执行到都会停。但如果是循环十万次你只想知道当v_index等于999时发生了什么手动按F10按到天亮也按不完。这时候就要用条件断点。在PL/SQL Developer里右键断点那个红色圆点选择Breakpoint Properties断点属性然后在Condition里输入一个布尔表达式。比如v_index 999这个断点只有在v_index等于999时才会触发其他时候直接跳过。条件里也可以写复杂一点比如v_status ERROR AND NVL(v_amount, 0) 0。条件断点用来排查偶发性问题特别有效。我处理过一个数据校验过程只有特定机构代码的数据会计算错误我在校验函数入口设了一个条件断点条件写成v_org_code A001然后一路往后走很快就定位到了那个机构特有的一条分支逻辑。如果没有条件断点就只能一遍遍让代码在无关数据上暂停。4.5 调用栈快速搞清“我是从哪来要到哪去”当断点停在某个深层函数里时很多人会忘记这个函数是被谁、在哪里调用的。这时候打开View - Call Stack调用栈就能看到从最顶层到当前行的完整调用链。比如显示的是proc_main - proc_validate - func_format_date点击调用栈里的任何一层代码窗口会自动跳到对应层级的调用处。这个功能在追查“参数为什么传错”时特别实用。我曾经调试一个包发现最终计算值时某个字段一直是空顺着调用栈往回翻才发现是上一层调用传参时把两个参数的位置调反了。这类问题如果不看调用栈光在函数内部看变量完全找不到根源。4.6 调试多个相关过程组合起来走一遍主流程你可能会问如果主过程调AA调BB又调C我是不是要分别启动三次调试不需要。最简单的办法是在最外层入口启动调试然后在中间每个关键过程入口处都设置断点用Step Into不断深入或者用Run从一个断点跳到下一个断点。这样整个调用链上的情况都能覆盖到而且变量值的变化过程是连续的不会被中断的会话状态打断。不过要注意Oracle的调试机制会有一些限制特别是在“跨会话调试”和“后台任务调试”比如DBMS_SCHEDULER跑的任务上。后台任务运行在数据库后台会话中你没法用PL/SQL Developer直接挂上去。遇到这种情况我基本会复制任务逻辑到本地过程里调试这比跟平台特性死磕效率高得多。5. 常见问题与排查技巧实录5.1 点击Start Debugger没反应或者直接跑完不进入调试这是新手遇到最多的问题原因绝大多数是对象没有用DEBUG模式编译。解决办法很简单先停止调试右键对象选择“编译并启用调试”重新编译后再点Start Debugger。第二种可能是你压根没打断点而整个过程又没什么执行代码所以一眨眼就跑完了。就像电影没设暂停点你按了播放自然一下就到结尾了。开局想逐行看的话建议在过程第一行可执行语句处打一个断点再启动调试。5.2 报ORA-01031: insufficient privileges怎么办这就是权限问题。用前面提到的GRANT语句给当前账号授予DEBUG CONNECT SESSION和DEBUG ANY PROCEDURE。如果调试的是别人Schema下的对象还需要单独对该对象授权GRANT DEBUG ON other_user.proc_calc TO dev_user;另外如果你是通过同义词访问的对象调试时尽量用原始对象名。同义词有时会让调试器找不到底层对象报错或者断点不生效。把Object Browser切到原始对象的属主下右键Test会稳很多。5.3 断点是灰色或者打上去没反应断点灰色一般意味着当前断点所在行的代码没有包含调试信息或者当前处于一个不被调试会话识别的地方。排查思路确认当前打开的是不是Test窗口对应的代码编辑区确认对象是不是刚刚用DEBUG模式重新编译过如果你在改的是包体确认编译的是包体而不是包头确认没有在“只读”状态下打开文件只读状态下断点没有意义实在不行Preferences里恢复Debugger相关的默认设置。有一个隐藏问题有时你改了代码但没有编译编辑器里显示的代码和数据库里实际编译的对象不一致调试器还是按数据库里那个旧版本跑。所以打不上断点时先看代码右下角或编译窗口提示的“Last Compiled”时间做到心里有数。5.4 Oracle 12c/19c版本和客户端位数不一致导致的调试异常有朋友反馈之前用PL/SQL Developer调试得好好的升级Oracle数据库到19c之后调试按钮灰了或者点了就闪退。这个问题很多时候不是PL/SQL Developer坏了而是客户端的兼容性问题。Oracle 12c之后调试相关机制有升级如果你的PL/SQL Developer版本太老可能跟不上。我的建议是用较新版本比如14/15/16都算稳定并且确保PL/SQL Developer的位数和Oracle客户端的位数一致。32位的PL/SQL Developer配64位的Oracle Instant Client容易出现OCI层面的崩溃。保险做法是装一个完整的32位Oracle Client然后让PL/SQL Developer指向那个客户端。还有就是本机装了多个Oracle客户端时PL/SQL Developer连接的OCI库可能不是你预期的那一个。在Preferences - Connection里检查OCI Library的路径如果不确定就换成明确版本的Instant Client目录。这类问题表面上看起来是“调试器坏了”实际是环境问题。5.5 存储过程里有COMMIT或ROLLBACK调试过程中断这个坑比较隐蔽。某些版本的PL/SQL Developer在调试过程中执行到COMMIT时会话会出现不可预知的异常比如调试器失去控制、变量值刷新失败等。如果你的代码里确实有显式的事务控制语句而你又必须调试它临时做法是先注释掉COMMIT或ROLLBACK等调完再恢复。如果逻辑又依赖事务状态那就退一层在最外层包入口调试别在事务密集的存储过程里反复单步。调完代码后一定要记得重新正常编译别把DEBUG模式的版本发布上去也不要留着注释过的事务语句在生产环境跑。5.6 快捷键速查效率提升最快的路径调试这事鼠标点来点去和快捷键操作的效率差一倍以上。这几个默认快捷键建议先背下来功能快捷键说明启动调试器F9或CtrlF9启动后会根据断点决定停止位置步过/单步F8不进入子过程调用直接执行完步入F7进入子过程内部逐步执行步出ShiftF7退出当前过程返回调用点运行到下一个断点CtrlF10没有断点时直接跑完设置/取消断点CtrlB光标所在行快速切换断点查看DBMS输出CtrlD打开DBMS Output面板不同版本、自定义配置下快捷键可能有差异但大部分默认配置都跑不出这个范围。建议你以自己版本里菜单或工具栏提示为准。顺手把常用的几个记牢确实能节省很多时间。5.7 我平时调试时的几个私货习惯最后说几个我自己的实操习惯不一定都写在官方文档里但确实帮我省了不少事。第一写新存储过程时我会第一时间用测试窗口跑一个“空跑”把入参填好启动调试如果有异常它会直接停在异常点。这样能在代码上线前把三种基本错误——语法错误、运行时异常、参数异常都暴露出来。第二调试函数时尽量把测试SQL和入参数据准备成一个小脚本保留下来。下次回归测试时直接复用脚本省得每次都要重新造数据。第三遇到“WHEN OTHERS THEN NULL”这种吞异常代码调试起来特别痛苦。如果必须调它我会临时把异常块改成“WHEN OTHERS THEN dbms_output.put_line(SQLERRM)”先看到错误再继续调完再恢复原样。这是没办法的办法但总比在一片黑暗里瞎猜强。第四凡是涉及日期、金额、组织机构的计算逻辑我调试时必定把边界值都试一遍比如0、负数、月末、年底、拼音大小写混用的编码。很多线上事故都是边界条件没覆盖到的结果。6. 尾声调试器不只是修bug的工具我个人这些年的体会是PL/SQL Developer的调试功能最被低估的地方恰恰不是“排错”而是“读代码”。当你接手一个别人写的几千行存储过程与其一行一行干读不如开个调试会话把入口参数填进真实环境的测试数据一步步走一遍。代码的执行路径、变量的变化过程、分支的走向都会变得非常清晰。这比任何文档都可靠因为代码不会骗你而注释和文档经常会。最后再分享一个小技巧调试的时候不要只盯着“走到哪儿了”还要时不时看看工具栏右侧那个“SQL”区域或状态栏那里往往能显示当前正在执行的SQL语句。很多性能问题、锁表问题就是在调试过程中顺带发现的。希望这篇关于PL/SQL Developer debug功能的梳理能帮你从只会print日志的“脚本侠”进化成真正靠调试器掌控代码每一个细节的PL/SQL开发者。以后不管碰到多复杂的存储过程先别慌开个调试会话慢慢走一遍答案往往就在你眼前。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表