OCI编程高级篇(十七) 直接路径装载总结

直接路径装载的过程其实跟普通插入没有太大的区别,只是设置更复杂,步骤觉得杂乱,其实理解了它的原理,就能自然知道需要哪些步骤。我们先看看插入数据有哪些问题。

  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行,看看循环设置数据入口和重置状态都在哪些位置来操作。

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

相关推荐
自不量力的A同学1 小时前
MySQL 9.2.0 的功能
数据库·mysql
XiaoLeisj2 小时前
【MySQL — 数据库增删改查操作】深入解析MySQL的 Update 和 Delete 操作
数据库·mysql
Mr.kanglong5 小时前
【MySQL】初始MySQL、库与表的操作
数据库·mysql
GIS小小研究僧6 小时前
PostgreSQL 数据库备份与还原
数据库·postgresql·oracle·postgis
西木Qi7 小时前
数据库备份、主从、集群等配置
数据库
qw9497 小时前
MySQL(高级特性篇) 13 章——事务基础知识
数据库·mysql
MXsoft6187 小时前
基于监控易一体化运维软件的浪潮服务器监控指标详解
运维·数据库
码农幻想梦7 小时前
实验十 数据库完整性实验
数据库·sql·oracle
小安同学iter7 小时前
MySQL数据库(二)
数据库·sql·mysql·oracle
努力成为头发茂密的程序员8 小时前
(0基础版,无需输入代码爬取)新手小白初步学习八爪鱼采集器
数据库·学习·数据分析