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() 指向的内存结构:
如果是字符串;内存结构如下, 当然这里是一元数组, 图中只是为了对于关系:
上图是将原始数据映射为可给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);
}
}
如有问题和交流请加