OCIStmtFetch2时出现Ora-01406错误

来源:互联网 发布:大屏幕电子书 知乎 编辑:程序博客网 时间:2024/06/08 05:39

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


原创粉丝点击