OCI编程高级篇(十七) 直接路径装载总结
访问www.tomcoding.com网站,学习Oracle内部数据结构,详细文档说明,下载Oracle的exp/imp,DUL,logminer,ASM工具的源代码,学习高技术含量的内容。
直接路径装载的过程其实跟普通插入没有太大的区别,只是设置更复杂,步骤觉得杂乱,其实理解了它的原理,就能自然知道需要哪些步骤。我们先看看插入数据有哪些问题。
1. 要插入数据的表名称是什么?表属于哪个schema?
2. 要插入的表有哪些字段啊?字段名称是什么?数据类型是什么?最大长度是多少?
3. 程序中的数据怎样和字段关联起来啊?
4. 怎样把程序数据转换成数据库的数据?
5. 怎样把数据库数据存储起来啊?
有了这些问题,我们看看直接路径装载是怎样解决这些问题的。
首先要分配一个句柄来存储表的信息,这个句柄叫做直接路径上下文dpctx,分配句柄后要设置它的属性,其中有表的schema信息,表的名称信息,表的字段个数。然后从dpctx中得到字段列表描述符,针对每个字段设置字段名称,数据类型,数据最大长度这些属性。接着要分配一个句柄来存放表的字段信息,这个句柄叫直接路径字段数组dpca,这个句柄就能把程序中的数据和装载的字段关联起来,使用OCIDirPathColArrayEntrySet()函数设置每行每个字段的数据。最后分配一个代表数据库数据的句柄,这个句柄叫做直接路径流dpstr,这个句柄代表数据库服务器端的数据。dpca代表OCI程序客户端的数据,通过OCIDirPathColArrayToStream()函数就能把客户端的数据转换成服务器端的数据格式,通过OCIDirPathLoadStream()函数就能指示服务器端的数据存储到表的块中。最后通过OCIDirPathFinish()函数提交装载的数据,相当于一般插入后的commit操作。
下面我们把前几节中的代码片段组合成一个完整的例子,看看直接路径装载的完整过程。还是以表test_tab为例,表的属主用户(schema)是scott,字段是ID,NAME和ADDR三个,装载4次数据,每次100行,看看循环设置数据入口和重置状态都在哪些位置来操作。
OCIEnv *envhp = NULL; OCIError *errhp = NULL; OCIServer *svrhp = NULL; OCISession *usrhp = NULL; OCISvcCtx *svchp = NULL;
struct dp_columns { ub4 dtyp; /* 字段类型 */ ub4 clen; /* 字段最大长度 */ char name[32]; /* 字段名称 */ }; int dp_load(void){ int i, r; sword rc, status; ub4 buf_sz; ub4 ncol; ub4 rowcnt; ub4 rowoff; ub4 cvtcnt; OCIDirPathCtx *dpctx; OCIDirPathColArray *dpca; OCIDirPathStream *dpstr; OCIParam *colLst = NULL; OCIParam *colDsc = NULL; struct dp_columms col[3]; ub4 id[100]; char name[100][32]; char addr[100][32]; /* 分配直接路径上下文句柄,父句柄是envhp */ rc = OCIHandleAlloc((void *)envhp, (void **)&dpctx, OCI_HTYPE_DIRPATH_CTX, 0, (void **)NULL); if (rc != OCI_SUCCESS) { fprintf(stderr, "Allocate direct path context error !\n"); return (-1); } /* 设置表的schema,在上下文句柄中设置 */ if (check_oci_error(errhp, OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX, (void *)"scott", strlen("scott"), (ub4)OCI_ATTR_SCHEMA_NAME, errhp) ) < 0) return (-1); /* 设置表名称,在上下文句柄中设置 */ if (check_oci_error(errhp, OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX, (void *)"test_tab", strlen("test_tab"), (ub4)OCI_ATTR_NAME, errhp) ) < 0) return (-1); /* 设置转换缓冲区的大小为2M,缓冲区大小要与字段数组相适应, * 太小的话一次转换缓冲区不够,会返回OCI_CONTINUE,需要再次转换 */ buf_sz = 2 * 1024 * 1024; if (check_oci_error(errhp, OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX, (void *)&buf_sz, 0, (ub4)OCI_ATTR_BUF_SIZE, errhp) ) < 0) return (-1); /* 设置表的字段个数 */ ncol = 3; if (check_oci_error(errhp, OCIAttrSet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX, (void *)&ncol, 0, (ub4)OCI_ATTR_NUM_COLS, errhp) ) < 0) return (-1); /* 获取字段列表描述符,设置字段信息 */ if (check_oci_error(errhp, OCIAttrGet((void *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX, (void *)&colLst, (ub4)0, (ub4)OCI_ATTR_LIST_COLUMNS, errhp) ) < 0) return (-1); /* 为了方便设置,我们定义一个结构存储字段信息 */ col[0].dtyp = SQLT_INT; col[0].clen = 8; strcpy(col[0].name, "ID"); col[1].dtyp = SQLT_CHR; col[1].clen = 30; strcpy(col[1].name, "NAME"); col[2].dtyp = SQLT_CHR; col[2].clen = 200; strcpy(col[2].name, "ADDR"); for (i=0; i<ncol; i++) { /* 获取字段描述符,这个描述符是隐式获得,需要释放,否则会造成内存泄露 * 这里字段的位置从1开始编号 */ if (check_oci_error(errhp, OCIParamGet((const void *)colLst, (ub4)OCI_DTYPE_PARAM, errhp, (void **)&colDsc, (ub4)(i+1)) ) < 0) return (-1); /* 设置字段的属性,字段名称 */ if (check_oci_error(errhp, OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM, (void *)col[i].name, (ub4)strlen(col[i].name), (ub4)OCI_ATTR_NAME, errhp) ) < 0) return (-1); /* 设置字段数据类型 */ if (check_oci_error(errhp, OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM, (void *)&col[i].dtyp, (ub4)0, (ub4)OCI_ATTR_DATA_TYPE, errhp) ) < 0) return (-1); /* 设置字段的数据最大长度 */ if (check_oci_error(errhp, OCIAttrSet((void *)colDsc, (ub4)OCI_DTYPE_PARAM, (void *)&col[i].clen, (ub4)0, (ub4)OCI_ATTR_DATA_SIZE, errhp) ) < 0) return (-1); } /* 释放掉字段描述符 */ OCIDescriptorFree((void *)colDsc, (ub4)OCI_DTYPE_PARAM); /* 设置完所有字段后,要把字段列表的描述符也释放掉 */ OCIDescriptorFree((void *)colLst, (ub4)OCI_DTYPE_PARAM); /* 设置完属性后,准备直接路径装载 */ if (check_oci_error(errhp, OCIDirPathPrepare(dpctx, svchp, errhp)) < 0) return (-1); /* 分配直接路径字段数组句柄,父句柄是dpctx */ rc = OCIHandleAlloc((void *)dpctx, (void **)&dpca, OCI_HTYPE_DIRPATH_COLUMN_ARRAY, 0, (void **)NULL); if (rc != OCI_SUCCESS) { fprintf(stderr, "Allocate direct path column array handle error !\n"); return (-1); } /* 分配直接路径流句柄,父句柄是dpctx */ rc = OCIHandleAlloc((void *)dpctx, (void **)&dpstr, OCI_HTYPE_DIRPATH_STREAM, 0, (void **)NULL); if (rc != OCI_SUCCESS) { fprintf(stderr, "Allocate direct path stream handle error !\n"); return (-1); } /* 循环4次,每次装载100行数据 */ for (r=0; r<4; r++) { /* 这里生成100行数据,在真实的环境中数据可能来自文件或其他数据源 * 为了演示方便,直接把name和addr设置成了相同的数据 */ for (i=0; i<100; i++) { id = i + 100; /* id从100开始计数 */ strcpy(name[i], "AAAAAAAAAA"); strcpy(addr[i], "BBBBBBBBBBBBBBBB"); } /* 循环设置每一行每一列的数据入口 */ for (i=0; i<100; i++) { /* 设置第一列ID的数据入口,列索引是从0开始的 */ if (check_oci_error(errhp, OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)0, (ub1*)&id[i], 4, OCI_DIRPATH_COL_COMPLETE) ) < 0) return (-1); /* 设置第二列NAME的数据入口,列索引是1 */ if (check_oci_error(errhp, OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)1, (ub1 *)name[i], (ub4)strlen(name[i]), OCI_DIRPATH_COL_COMPLETE) ) < 0) return (-1); /* 设置第三列ADDR的数据入口,列索引是2 */ if (check_oci_error(errhp, OCIDirPathColArrayEntrySet(dpca, errhp, (ub4)i, (ub2)2, (ub1 *)addr[i], (ub4)strlen(addr[i]), OCI_DIRPATH_COL_COMPLETE) ) < 0) return (-1); } /* 重置直接路径字段数组的状态 */ if (check_oci_error(errhp, OCIDirPathColArrayReset(dpca, errhp)) < 0) return (-1); /* 重置直接路径流的状态 */ if (check_oci_error(errhp, OCIDirPathStreamReset(dpstr, errhp)) < 0) return (-1); /* 转换字段数组到流数据,然后装载流数据,一共转换100行数据,从第0行开始 */ rowcnt = 100; rowoff = 0; while (1) { status = OCIDirPathColArrayToStream(dpca, dpctx, dpstr, errhp, (ub4)rowcnt, (ub4)rowoff); if (status == OCI_CONTINUE) { /* 转换缓冲区过小,数据没有转换完,得到已经转换的行数 */ if (check_oci_error(errhp, OCIAttrGet((const void *)dpca, OCI_HTYPE_DIRPATH_COLUMN_ARRAY, (void *)(&cvtcnt), (ub4 *)0, OCI_ATTR_ROW_COUNT, errhp) ) < 0) return (-1); rowcnt -= cvtcnt; rowoff += cvtcnt; } else { if (check_oci_error(errhp, status) < 0) return (-1); } /* 装载数据 */ if (check_oci_error(errhp, OCIDirPathLoadStream(dpctx, dpstr, errhp)) < 0) return (-1); /* 如果前面的转换全部完成,现在装载也成功了,说明这100条数据装载完成 * 退出while()循环 */ if (status == OCI_SUCCESS) break; /* 程序到这里,说明还有未转换完的数据,重置直接路径流的状态,进行下一次转换, * 下次转换从新的rowoff位置开始,转换条数为新的rowcnt条 */ if (check_oci_error(errhp, OCIDirPathStreamReset(dpstr, errhp)) < 0) return (-1); } /* while()循环的结束边界 */ /* 装载完了100条数据,进入下一循环,装载另外的100条数据,或者全部装载完退出循环 */ } /* for()循环的结束边界 */ /* 数据全部装载完,提交装载的数据 */ if (check_oci_error(errhp, OCIDirPathFinish(dpctx, errhp)) < 0) return (-1); /* 释放前面分配的句柄 */ OCIHandleFree((void *)dpca, OCI_HTYPE_DIRPATH_COLUMN_ARRAY); OCIHandleFree((void *)dpstr, OCI_HTYPE_DIRPATH_STREAM); OCIHandleFree((void *)dpctx, OCI_HTYPE_DIRPATH_CTX); return (0); }
到这里OCI的直接路径装载就介绍完了,上面的例子演示了一个完整的过程,包括数据转换中遇到的一些问题的解决方法,多看几遍就能对直接路径装载的流程全面掌握。

浙公网安备 33010602011771号