使用 dbms_output.put_line() 列印執行資訊是常用的 Oracle Stored Procedure 偵錯技巧,以下 Procedure 範例在DELETE 及 INSERT 後透過 dbms_output.put_line() 印出影響資料筆數,概念跟在程式碼裡塞入一堆 Debug.Print、MsgBox、alert() 差不多,是執行期間追查問題的重要線索:
createor replace procedure JeffDBJobTest1 is
begin
deletefrom JEFFTEST where idx = 32;
dbms_output.put_line(sql%rowcount || ' rows deleted');
insertinto JEFFTEST values (32, sysdate);
dbms_output.put_line(sql%rowcount || ' rows inserted');
end;
使用 PL/SQL Developer 或 Toad 等 Oracle 資料庫工具執行 Procedure,軟體介面有地方可以檢視 dbms_output 的輸出訊息,除錯抓蟲時很有用。
這個技巧開發測試階段大家用得很順手,如果程式已經上線在正式環境,是否也有機會蒐集到這些珍貴偵錯情資呢?跟同事討論到這個問題,起初大家都覺得無解,認真爬文找到線索,經過一番摸索及踩坑,還真的可行。
整理重點如下:
- dbms_output.put_line() 所寫入的內容會被放在緩衝區( Buffer )中( 緩衝區容量預設 20,000 Bytes ),可透過 dbms_output.get_line() 或 .get_lines() 讀取,若光寫不讀會把緩衝區塞爆出錯。
- 緩衝區以 Session 為單位,依實務的角度,就是你必須在執行 Procedure 的 OracleConnection 執行 dbms_output.get_line() 才讀得到東西。像 Dapper 允許不必開啟連線就執行 .Execute()/.Query() (背後自動開啟、關閉),就可能因 Procedure 執行與 dbms_output 讀取使用不同連線( Session )而讀不到資料。
- dbms_output 預設為停用,記得要先呼叫 dbms_output.enable() ( 就是上圖有個 Enable Chceckbox 開關的意義 ),不然會做白工。
- dbms_output.get_line(line, status)有兩個輸出參數,每次讀取一列字串,line 為字串內容,status 傳回 0 表示還有下一筆,傳回 1 代表緩衝區已空;dbms_output.get_lines(lines, numlines)則一次取回字串陣列( CHARARR 型別 )及資料筆數。
講完原理來實際演練,我用 Dapper + ODP.NET 示範,用 get_line() 加 while 迴圈讀取,get_lines() 得取回字串陣列型別比較囉嗦,以後再試:
staticvoid Main(string[] args)
{
using (var cn = new OracleConnection(cs))
{
//**重要** 先開啟連線,確保後續執行在同一個Session
cn.Open();
//**重要** 記得要啟用dbms_output
cn.Execute("dbms_output.enable",
commandType: CommandType.StoredProcedure);
//呼叫Stored Procedure
cn.Execute("JeffDbJobTest1",
commandType: CommandType.StoredProcedure);
//準備參數接收
DynamicParameters p = new DynamicParameters();
p.Add("line", dbType: DbType.String,
direction: ParameterDirection.Output, size: 4000);
p.Add("status", dbType: DbType.Int32,
direction: ParameterDirection.Output);
int status;
do
{
cn.Execute("dbms_output.get_line", p,
commandType: CommandType.StoredProcedure);
Console.WriteLine(p.Get<string>("line"));
status = p.Get<int>("status");
} while (status == 0);
}
}
測試成功!