金仓数据库KingbaseES———DCI中的大对象处理
关键字:
DCILobLocator、CLOB、BLOB、人大金仓、KingbaseES
DCI中的DCILobLocator定义
struct DCILobLocator
{
DCIHandleHead head; // 结构体头部,head type= DCI_DTYPE_LOB
ub4 loid;
lobType type; // 初始为0,1 for BLOB(lobType_BLOB)0, 2 for CLOB(lobType_CLOB)
int len; // buf数组中已有数据的长度bytes for BLOB, characters for CLOB
int lobj_fd;//判断定位器是否打开的标志,标记是否为第一次读取或者写入,初始值为-1,表示第一次读取{-1,1}
char open_manually;//是否手动开启
char doBegin;
char enable_buffer;//是否打开lob定位器缓冲功能
char *buf;//初始大小为1048576个字节,即1M,后续加上偏移量offset,始终指向首地址
char updated;//
char status_piece; //piece flag.Must be use DCI_FIRST_PIECE,then you can use DCI_NEXT_PIECE and DCI_LAST_PIECE
char status_offset; //offset flag.LOB is opend,you can set offset.
int real_wlen; //actual data length to write实际写入的数据量
int need_wlen; //data length needed to write存储需要写入的数据量,在读取数据量大于真实传入数据量时使用
int remain_rlen; //未读取的数据长度data length not read
char is_init;//定位器是否已经初始化
DCIEnv *pEnv; //环境句柄
char *cur;//初始与buf地址相同,随着数据量的增大,指向buf数组的已有内容的最后一个字符
int len_in_byte;//初始值为-1,按照字节计数的长度
int allocated;//已分配空间大小,对应buf数组申请的空间大小,最大支持2G的空间分配,初始值为-1
char *backup_buf;//备份区域,大小为backup_len
int backup_len;//备份区域的大小
int backup_len_in_byte;//以字节长度衡量的备份区域的大小
int is_temporary;//是否为临时LOB,临时大对象无需执行写入数据库操作
char *table_name; //表名称need to invoke free() when dealloc
char *column_name; //列名称need to invoke free() when dealloc
CtidXminListNode *pCtidXminListNode; //存储Ctid、Xmin,need to invoke free() when dealloc
unsigned char mode; //DCILobOpen()调用设置LOB定位器的执行模式READ WRITE FLAG DCI_LOB_READONLY DCI_LOB_READWRITE
};
DCI中的大对象读操作
在大对象读方面,DCILobRead( )支持回调,BLOB与CLOB的长度用同一个参数amtp传入,对于CLOB类型,长度为字节数或者字符数,仅支持UTF8,GB18030,GBK三种编码方式;对于BLOB类型,长度为字节数。DCILobRead2( )暂时不支持回调,BLOB与CLOB的长度用不同的参数表示,大对象占用的字节数用byte_amtp表示,对于BLOB,该参数始终生效,对于CLOB,只有当char_amtp为0时才生效,字符数用参数char_amtp表示,仅用于CLOB,对BLOB应当忽略。
大对象的查询过程同样遵循OCI操作的一般过程,经历建立连接、语句句柄分配DCIHandleAlloc( )、查询语句准备DCIStmtpPrepare( )、查询语句执行DCIStmtExecute( )、获取查询结果DCIStmtFetch( )、查询事务提交DCITransCommit( )的过程,有所区别的是对于大对象类型,需要调用DCIDescriptorAlloc( )分配OCI_DTYPE_LOB类型的大对象句柄,并调用DCIDefineByPos( )对各个句柄与对应的类型SQLT_CLOB与SQLT_BLOB进行位置绑定,将大对象句柄关联到语句句柄stmtp对应的pDefine的valuep成员上:
err = OCIDescriptorAlloc(pEnv, (void**)&pBlob, OCI_DTYPE_LOB, 0, NULL);
err = OCIDefineByPos(pStmt,
&pDefine,
pError,
1,
(dvoid *) &pBlob,
sizeof(OCILobLocator *),
SQLT_BLOB,
(void *)0,
(ub2 *)0,
(ub2 *)0,
OCI_DEFAULT);
//在OCIDefineByPos内部:
stmtp->pDefine[position - 1].valuep = valuep;
在DCIStmtpFetch( )内部,需要调用KSAPI_BindCol( )再次将用户缓冲区pDefine[i].pBuffer->buf与数据库的列进行绑定:
ret = KSAPI_BindCol(stmtp->hstmt, pDefine[i].position, KSQL_C_BINARY, (void *)&pDefine[i].pBuffer->buf, SQLT_BLOB_LENGTH, ind);
ret = KSAPI_BindCol(stmtp->hstmt, pDefine[i].position, KSQL_C_CHAR, (void *)&pDefine[i].pBuffer->buf, SQLT_CLOB_LENGTH, ind);
最后将pDefine[i].pBuffer->buf中的数据存入DCILobLocator的buf成员变量中,以便后续从中读取。特别的,针对select…for update语句,需要额外关联该行数据的唯一标识ctid与xmin,因此需要申请空间并将其关联到DCILobLocator的pCtidXminListNode成员中,便于后续的写或者更新操作。针对大对象,DCIStmtFetch()的处理流程如图1所示。
图1 DCIStmtFetch( )处理大对象的流程
大对象因为数据体量比结构化数据大,且数据组织方式不规范,需要依次提取到相应的文件并做其他的处理。因此将数据读取到DCILobLocator中之后,还需要调用DCILobRead( )分批次进行读取。由于存放数据的缓冲区大小受限(READ_BUFSIZE=1024),因此每次只能读取一部分数据,需要采用循环结构,读取返回OCI_NEED_DATA时表示本次读取成功,数据还未读取完成,需进入循环重新读取,返回OCI_SUCCESS表明所有数据都已经读取成功。特别地,对于CLOB类型的数据,其读取缓冲区大小为READ_BUFSIZE + 1。DCILobRead( )其具体的实现思路为:
Step1:参数状态判断:svchp、errhp、locp以及locp中已有数据的长度;
Step2:判断bufp;
Step3:同步svchp->pEnv->pSveCtx与当前svchp句柄,加线程锁;
Step4:根据locp->type判断是读取BLOB还是CLOB;
Step5:读取调用的返回值处理。
在DCILobRead( )函数内部,由于BLOB与CLOB对应的计量方式不同,需要采取不同的方式从DCILobLocator中读取,对应的函数分别为DCILobReadBLOB( )与DCILobReadCLOB( ),其具体的实现思路分别如图2所示。相较于DCILobReadCLOB( ),DCILobReadBLOB( )没有字符编码判断这一步骤,其余实现步骤基本一致。
图2 DCILobRead( )实现过程
DCI中的大对象写操作
在DCI中,LOB大对象的写操作需要借助DCILobLocator,调用DCILobWrite( )写入,一次只可写入一列数据。而对于LOB大对象的更新操作,需要使用select…for update语句获取到该行,在本地修改之后,调用DCILobWrite( )重新写入,在此期间,ctid与xmin作为该行数据的唯一标识,需要被获取并关联到DCILobLocator中,对应DCIStmtFetch( )中对stmtp->hasForUpdate的处理。同大对象数据读取一样,大对象写操作过程也受字符编码的影响,因此针对CLOB与BLOB分别对应DCILobWriteCLOB( )与DCILobWriteBLOB( ),具体的实现思路为:
Step1:判断写入内容bufp是否为空;
Step2:判断svchp->pEnv->pSveCtx != svchp,确保当前服务上下文句柄与环境句柄中服务上下文句柄一致;
Step3:根据定位器的type属性判断是写入BLOB(1)还是CLOB(2)。
对于DCILobWriteBLOB( ),其实现过程如图3所示。
图3 DCILobWriteBLOB( )实现过程
写入过程中包含为两个步骤:writeToBLOB( )与writeLobToDB( )。writeToBLOB( )是将数据写入DCILobLocator( )并设置对应的成员变量,而writeLobToDB( )是将DCILobLocator中的数据真实写入数据库。
图4 writeToBLOB( )与writeLobToDB( )实现过程
对于DCILobWriteCLOB( ),其实现过程如图5所示。
图5 DCILobWriteCLOB( )实现过程
DCI对大对象操作函数的兼容情况
DCI中支持两种大对象类型,分别为字符大对象类型SQLT_CLOB,二进制大对象类型SQLT_BLOB。Oracle,KINGBASE V7,PG的大对象都是采用的OID中转方式,即用户表中的大对象实际只存一个定位器,指向系统的大对象表中对应的位置。大对象的实际存储位置和用户表的位置是分开的。所以读写大对象都是靠定位器来中转实现,可以通过LOB函数直接把大对象读写到数据库,不需要像普通字段一样,非得等到insert/update语句时才能写入数据库。例如:可以通过获取select查询返回的结果中的大对象定位器LOBLocater。而KINGBASE V8的大对象实际采用的是BYTEA和TEXT的直接存储方式,和普通类型一样,没有OID这种定位器,也就无法通过定位器直接写入数据库。但是需要保持原OCI对外提供的功能和接口调用形式不变,所以需要实现客户端大对象的各种操作,目前已经兼容了23个大对象操作函数。
由兼容对比可以看出,DCI已经实现了OCI操作LOB的基本功能,但就数据量而言,DCI使用KDB_TYPE_CLOB_LENGTH来标识最大支持的CLOB的数据量,为1GB,用KDB_TYPE_BLOB_LENGTH标识操作BLOB的最大数据量,为2GB,暂不支持4GB以上的大对象操作。
表1 OCI与DCI大对象操作函数实现
OCI接口 | 功能 | DCI接口 |
将一个LOB附加到另一个 | DCILobAppend() | |
OCILobAssign() | 将一个LOB定位器分配给另一个,不能用于临时LOB | DCILobAssign() |
OCILobCharSetForm() | 从LOB定位器获取字符集形式 | DCILobCharSetForm() |
OCILobCharSetId() | 从LOB定位器获取字符集ID | DCILobCharSetId() |
OCILobClose() | 关闭先前打开的LOB | DCILobClose() |
OCILobCopy2() | 将一个LOB的全部或部分复制到另一个,必须用于大于4GB的LOB | DCILobCopy() |
OCILobCreateTemporary() | 创建一个临时LOB | DCILobCreateTemporary() |
OCILobFreeTemporary() | 释放临时LOB | DCILobFreeTemporary() |
OCILobIsTemporary() | 确定给定的LOB是不是临时的 | DCILobIsTemporary() |
OCILobDisableBuffering() | 关闭LOB缓冲 | DCILobDisableBuffering() |
OCILobEnableBuffering() | 打开LOB缓冲 | DCILobEnableBuffering() |
OCILobFlushBuffer() | 刷新LOB缓冲区 | DCILobFlushBuffer() |
OCILobErase2() | 擦除LOB的一部分,必须用于大于4GB的LOB | DCILobErase() |
OCILobLocatorIsInit() | 检查LOB定位器是否已经初始化 | DCILobLocatorIsInit() |
OCILobOpen() | 打开一个LOB | DCILobOpen() |
OCILobRead2() | 读一部分LOB,必须用于大于4GB的LOB | DCILobRead()& DCILobRead2() |
OCILobWrite2() | 写入LOB,必须用于大于4GB的LOB | DCILobWrite() |
OCILobIsEqual() | 比较两个LOB定位器指向的LOB是否相等 | DCILobIsEqual() |
OCILobTrim2() | 截断LOB,必须用于大于4GB的LOB | DCILobTrim() |
OCILobGetLength2() | 获取LOB的长度,必须用于大于4GB的LOB | DCILobGetLength() |
OCILobIsOpen() | 检查LOB是否打开 |
表1 OCI与DCI大对象操作函数实现(续)
OCI接口 | 功能 | DCI接口 |
OCIDurationEnd() | 临时LOB的最终用户持续时间 | DCIDurationEnd() |
OCIDurationBegin() | 启动临时LOB的用户持续时间 | DCIDurationBegin() |
OCILobArrayRead() | 读取多个定位器的LOB数据 | |
OCILobArrayWrite() | 为多个定位器写入LOB数据 | |
OCILobFileClose() | 关闭以前打开的BFILE | |
OCILobFileCloseAll() | 关闭所有以前打开的文件 | |
OCILobFileExists() | 检查服务器上是否存在文件 | |
OCILobFileGetName() | 从LOB定位器上获取目录对象和文件名 | |
OCILobFileIsOpen() | 用定位器检查服务器上的文件是否已经打开 | |
OCILobFileOpen() | 打开一个BFILE | |
OCILobFileSetName() | 在LOB定位器中设置目录对象和文件名 | |
OCILobGetChunkSize() | 获取LOB的块大小 | |
OCILobGetContentType() | 检索SecureFile中用户指定的内容类型字符串 | |
OCILobGetOptions() | 获取SecureFile的选项设置 | |
OCILobGetStorageLimit() | 以字节为单位获取内部LOB的最大长度 | |
OCILobLoadFromFile2() | 从BFILE加载LOB,必须用于大于4GB的LOB | |
OCILobLocatorAssign() | 将一个LOB定位器分配给另一个,可用于临时LOB | |
OCILobSetContentType() | 存储SecureFile的用户指定的内容类型字符串 | |
OCILobSetOptions() | 为现有和新创建的SecureFiles的选项设置 | |
OCILobWriteAppend2() | 写入从LOB结尾开始的数据,必须用于大于4GB的LOB |