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