天天看點

OCIStmtFetch2時出現Ora-01406錯誤

1. 場景:

使用OCIStmtFetch2批量導出表資料(一次1000條,one_batch=1000),

如果OCIStmtFetch2成功,則将導出的資料寫入檔案;

如果OCIStmtFetch2傳回OCI_NO_DATA,則使用OCIAttrGet找到最後一次導出的不滿1000條的資料條數,也将其内容寫入檔案;

否則,報錯退出;

2. 發現的問題:

如果表中記錄數<1000條(one_batch),那麼程式不會報錯退出,最終也會得到檔案(本以為最終得到了正确檔案,其實已經是資料被truncate之後的内容了);

但是一旦表中資料記錄數>=1000條(one_batch),則程式會在OCIStmtFetch2時報出Ora-01406:fetched column value was truncated錯誤

3. 探究過程:

我定義的存儲字段的結構體定義類似如下:

typedef struct {

short indicator;

short len;

char val[1024];

}dbo_str_t;

我申請的用來存放fetch資料的記憶體大小為

sizeof(dbo_str_t)*col_num*one_batch#col_num為fetch出來的字段個數

注意到表中有一個字段f_content的DB類型是CHAR(1024)【即使原本想存入内容沒有1024位元組,資料庫也會自動在末尾補足空格至1024位落庫】,而fetch時會在字段末尾加上\0作為結束符,于是實際長度就成了1025,大于為那個字段申請的記憶體長度

又因為程式表現出不滿1000不報錯,滿了就報錯。是以原本以為是當資料量到達1000條時,最後一條資料由于最後多了一個\0是以越界寫導緻該錯誤發生。

但是在後續切實寫報告的時候發現,那個超長的字段綁定的并非記憶體中最後一個域,是倒數第二個域,那麼即使它超長越界寫也隻會寫到倒數第一個域的頭上,而不會引發越界寫而報錯

于是又做了更多的實驗發現,發現當條數超過1000報錯退出時,綁定的記憶體中也是有資料的,而且其中的indicator=1024,查資料發現(這裡推薦一個非常好的oci資料網站https://docs.oracle.com/cd/B28359_01/appdev.111/b28395/oci17msc001.htm#i574982),當indicator>0時,表示該字段在從資料庫取到記憶體時已經發生了truncate,indicator所示的值是truncate前資料的原本長度。

又發現即使是程式正常退出,而且檔案已生成,相應字段的indicator也是1024

也就是說,不管程式是正常結束還是報錯退出,實際上該字段都發生了截斷(後續檢視所生成的檔案發現最後一位沒有,也印證了這個想法)

那為什麼不足1000條時程式還能正常退出、生成檔案呢?

4. 結論

後來檢視代碼推測,當條數不滿one_batch、且其中會發生truncate時調用OCIStmtFetch2,這個接口會優先傳回OCI_NO_DATA,而不是-1(Ora-01406),于是後者的錯誤資訊就被掩蓋了(對于這種一次執行結果中包含多種錯誤的情況,還不知道怎麼利用OCIErrorGet擷取到所有的錯誤資訊,如果有知道的朋友,希望可以不吝賜教)

而我的程式未再對fetch出來的資料中的完整性做判斷,于是就将truncated data寫入檔案并正常退出

另外糾正之前的一個錯誤了解:因為在OCIDefineByPos時已經聲明了每個字段導出時的記憶體長度,是以資料庫不會取出内容後強行往裡面放導緻越界寫,而是先将字元串截取至之前聲明的長度然後用indicator來提示發生了truncate