Oracle 批量提交,批量绑定 OCIBindByName 和OCIBindObject 的使用

穷遍所有OCI文档找不出一个能绑定多行数据的说明和示例,自己尝试快两周解决了Oracle Spatial 批量绑定将Oracle的写入效率提升到了5000行左右,以下是一点心得

Oracle OCI  基本操作 本文不多说,假设你会用基本的OCI操作数据,但是不知道怎么用OCIBindByName  一次可以绑定级别上千行数据, 本文将对你有点用

 从Statement 说起,分以下三步

1: 准备sql:  

 OCIStmtPrepare (m_h,   //OCIStmt 指针

                             m_ErrorHandle,   //OCIError指针

                            (text *)sql, //sql 语句
                            (ub4)strlen((const char *)sql),  //SQL语句长度

                         OCI_NTV_SYNTAX, 
                         OCI_DEFAULT))),

2: 绑定写入的数据,  可以是一行数据,也可以是多行数据

2.1非对象绑定:

一行数据:

OCIBindByName(stmt, &m_h, err, (const OraText*)name, strlen(name),
            BufferPtr(), BufferSize() , dty, indp, 0,0,0,0, (ub4)bindtyp))

单行数据BufferPtr 就是数据的指针,Oracle 可操作的就只有OCINumber,OCIDate,OCI*** 等oci自定义类型

BufferPtr的实际分配长度要比实际数据多1字节,

字符串的BufferSize() 需要比strlen求出来的长度大一,并且最后一位的值为'\0'

一个示意图: 

多行数据:

   OCIBindByName(stmt, &m_h, err, (const OraText*)name, strlen(name),
            (void*)Ptr(), maxLength, dty, (void*)(&m_indicator[0]), 0, 0, 0, 0, (ub4)bindtyp)))

  OCIBindArrayOfStruct(m_h, err, (ub4)maxLength, 0,0, 0);

Ptr() 指向的内存结构:

如果是字符串;内存结构如下, 当然这里是一元数组, 图中只是为了对于关系:

Oracle 批量提交,批量绑定 OCIBindByName 和OCIBindObject 的使用

上图是将原始数据映射为可给OCIBindbyName输入的数据,

如果是绑定数值,OCIDate,OCINumber , 则这个Ptr数据内存就是OCIDate,OCINumber对象数组, maxlength 用sizeof(OCIDate)sizeof(OCINumber)表示. 不用特别处理.

maxLength 代码每行元素中一个元素的最大长度, 不是Ptr() 这个指针内存的所有长度

m_indicator 是一个数组,代表Ptr()这个指针内存中每个对象的实际长度,  如果设置为-1,  Oracle 将绑定NULL

设置其他>=0的值为有效,(PS:这里有个大坑误区, 这里没有指明 这个实际数组的长度, Oracle如何知道我们数组的长度,我们一般API都要指明, 而OCI的数组长度都由 OCIStmtExecute的第四个参数指定)

        

2.2 对象绑定

先调用OCIBindByName  获得OCIBInd 对象,再调用OCIBindObject  绑定一个对象或者多个对象

OCIBindByName 初始化的时候, dty参数为SQLT_NTY,其他参数全为0都可以, 因为oci会忽略其他参数, 不管单个绑定还是多个,

单个Object 绑定        

简单介绍下SDOGeometry这个对象是Oracle 自定义对象, 表示几何对象, 里面两个数组存不固定的坐标,以及固定长度的数据头描述, 这种网上很多不过多介绍了.

OCIBindObject(bind->m_h, stmt->ErrorHandle(),
        m_pMetaTable->SDO_GEOMETRY_TYPE()->Type(), 
       (void**)sdogeometry.SDOGeometryPtrAdd(), 0,
         (void**)sdogeometry.SDOGeometryIndPtrAdd(), 0);

没有任何可说明的, 传对象和类型可搞定.

多个Object绑定

先包装数组指针,   并且m_indszp参数必须是ub4数组(PS:OCI官网说是sb2, 我看Oracle写文档的真是sb, ),这个数组直接存每个对象的描述长度,这里描述长度是倒数第二个参数里面元素的sizeof长度, 我这是写入Oracle Spatial 空间数据,所以每个m_indszp元素其实就是一个固定描述长度18.

 for (int i = 0; i < nCount; i++)
    {
        m_pGeomCache.push_back(sdoBuilder[i]->SDOGeometryPtr());
        m_pGeom_inCache.push_back(*sdoBuilder[i]->SDOGeometryIndPtrAdd());
        m_indszp.push_back(sizeof(sdo_geometry_ind));
    }

OCIBindObject(bind->m_h, stmt->ErrorHandle(),
        m_pMetaTable->SDO_GEOMETRY_TYPE()->Type(), 
        (void**)(&(m_pGeomCache[0])), 0,
        (void**)&m_pGeom_inCache[0], &m_indszp[0]);

这里没有研究blob 绑定, 这里没尝试,简单猜想跟普通blob 绑定应该类似

普通blob 绑定是在OCIStmtExecute后执行OCILobWrite完成的, 

OCILobWrite(pConn->m_h, err, 
        m_LobDesc.m_h, &len, 1, 
        PtrT<dvoid*>(), len, 
        OCI_ONE_PIECE, NULL, 0, 0, 0 )

这里很可能是多次调用这个函数, 因为OCI_ONE_PIECE 这个就是跟游标类似的写法.

 

3: 执行绑定

这里只有一个参数需要注意, nArrayCount 这就是我们每个OCIBindByName绑定的数据条数了.

OCIStmtExecute(*pConn,
            m_h,
            m_ErrorHandle,
            (ub4)nArrayCount,
            (ub4)0,
            (CONST OCISnapshot *) 0,
            (OCISnapshot *)0,
            (ub4)OCI_BATCH_ERRORS)

 

另外想获得那条出错, 可以使用以下代码:

        ub4   num_errs; 
    
        COCIErrorHandle  errhp2(m_EnvHandle);
        OCIAttrGet(m_h, OCI_HTYPE_STMT, &num_errs, 0, OCI_ATTR_NUM_DML_ERRORS, errhp2);
        if (num_errs) {
            GsString str;
            for (int i = 0; i < num_errs; i++)
            {
                OCIError* errortmp;
                sword h = OCIHandleAlloc((void *)m_EnvHandle, (void **)&errortmp,
                    (ub4)OCI_HTYPE_ERROR, 0, (void **)0);
            
                OCIParamGet(m_ErrorHandle.ErrorHandle(), OCI_HTYPE_ERROR, errhp2.ErrorHandle(), (void**)(&errortmp), i);
                sword errorcode = 0;
                int rowoffset = 0;
                OCIAttrGet(errortmp, OCI_HTYPE_ERROR, &rowoffset, 0, OCI_ATTR_DML_ROW_OFFSET, errhp2.ErrorHandle());
                text errbuf[512];
                (void)OCIErrorGet((dvoid *)errortmp, (ub4)1, (text *)NULL, &errorcode,
                    errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR);
                 str = GsEncoding::ToLocal((const char*)errbuf);
                 OCIHandleFree((dvoid *)errortmp, (ub4)OCI_HTYPE_ERROR);
            }
        }

如有问题和交流请加

Oracle 批量提交,批量绑定 OCIBindByName 和OCIBindObject 的使用