人大金仓数据库KingbaseES DCI中的大对象处理

99 阅读9分钟

金仓数据库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( )没有字符编码判断这一步骤,其余实现步骤基本一致。

image.png

图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所示。

image.png

image.png

图3 DCILobWriteBLOB( )实现过程

写入过程中包含为两个步骤:writeToBLOB( )与writeLobToDB( )。writeToBLOB( )是将数据写入DCILobLocator( )并设置对应的成员变量,而writeLobToDB( )是将DCILobLocator中的数据真实写入数据库。

image.png

图4 writeToBLOB( )与writeLobToDB( )实现过程

对于DCILobWriteCLOB( ),其实现过程如图5所示。

image.png

图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接口
OCILobAppend()将一个LOB附加到另一个DCILobAppend()
OCILobAssign()将一个LOB定位器分配给另一个,不能用于临时LOBDCILobAssign()
OCILobCharSetForm()从LOB定位器获取字符集形式DCILobCharSetForm()
OCILobCharSetId()从LOB定位器获取字符集IDDCILobCharSetId()
OCILobClose()关闭先前打开的LOBDCILobClose()
OCILobCopy2()将一个LOB的全部或部分复制到另一个,必须用于大于4GB的LOBDCILobCopy()
OCILobCreateTemporary()创建一个临时LOBDCILobCreateTemporary()
OCILobFreeTemporary()释放临时LOBDCILobFreeTemporary()
OCILobIsTemporary()确定给定的LOB是不是临时的DCILobIsTemporary()
OCILobDisableBuffering()关闭LOB缓冲DCILobDisableBuffering()
OCILobEnableBuffering()打开LOB缓冲DCILobEnableBuffering()
OCILobFlushBuffer()刷新LOB缓冲区DCILobFlushBuffer()
OCILobErase2()擦除LOB的一部分,必须用于大于4GB的LOBDCILobErase()
OCILobLocatorIsInit()检查LOB定位器是否已经初始化DCILobLocatorIsInit()
OCILobOpen()打开一个LOBDCILobOpen()
OCILobRead2()读一部分LOB,必须用于大于4GB的LOBDCILobRead()&DCILobRead2()
OCILobWrite2()写入LOB,必须用于大于4GB的LOBDCILobWrite()
OCILobIsEqual()比较两个LOB定位器指向的LOB是否相等DCILobIsEqual()
OCILobTrim2()截断LOB,必须用于大于4GB的LOBDCILobTrim()
OCILobGetLength2()获取LOB的长度,必须用于大于4GB的LOBDCILobGetLength()
OCILobIsOpen()检查LOB是否打开DCILobIsOpen()

表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

参考资料

KES客户端编程接口及框架简介