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

相关推荐
BestandW1shEs2 小时前
谈谈Mysql的常见基础问题
数据库·mysql
重生之Java开发工程师2 小时前
MySQL中的CAST类型转换函数
数据库·sql·mysql
教练、我想打篮球2 小时前
66 mysql 的 表自增长锁
数据库·mysql
Ljw...2 小时前
表的操作(MySQL)
数据库·mysql·表的操作
哥谭居民00012 小时前
MySQL的权限管理机制--授权表
数据库
wqq_9922502772 小时前
ssm旅游推荐系统的设计与开发
数据库·旅游
难以触及的高度3 小时前
mysql中between and怎么用
数据库·mysql
Jacky(易小天)3 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
Karoku0664 小时前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch