访问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的直接路径装载就介绍完了,上面的例子演示了一个完整的过程,包括数据转换中遇到的一些问题的解决方法,多看几遍就能对直接路径装载的流程全面掌握。