引言
随着web访问、移动设备、报表和分析包以及其他应用的发展,数据库市场正在快速地创新。然而,数量众多的系统因为不是以关系方式来组织自己的数据,它们仍无法充分参与到这些创新中。由于无法使用关系APIs(如SQL、ODBS、JDBC、PHP、ADO.Net等)有效率地访问数据,很多系统不能有效地应用这些创新。
本文将为一个很特殊的挑战提供一个解决方案:在同一个文件中混合着不同的记录结构,并使用某个字段告知应用程序如何解释每条记录。如果你拥有现代关系数据库相关的技能,你听到这些可能会觉得很奇怪。你学到要遵循某个严格的模式,并通过将数据分离到不同的表来规范化数据。然而并不总是按照这种方式编写业务软件的。在不远的过去,曾经在操作系统级别就限制了文件句柄总数。比如,在DOS操作系统时代,文件句柄数加上stdin、stdout、stderr等最大总数为20个。
虽然对于非关系或NoSQL APIs来说,混合记录结构是适当的,因为这儿不需要模式,但开发者经常会面临要使用某个需要一个模式的技术的场合。对于某些使用SQL(或ODBC等)的软件一个一致的模式定义是必需的,比如大量的第三方报表和分析工具,包括水晶报表和Excel,以及很多web和移动设备应用。
当某个业务需要使用SQL来访问该类型数据时,它们通常借助于运行批处理程序来提取数据这样的技巧将数据转换成某个关系格式,再将它们加载进关系数据库表。这些批处理程序可能不得不彻夜运行,所以这些被导入的数据很少能保持处于最新的状态。另外一个选择就是添加一个SQL API,而这需要将每种记录结构分解到自己单独的物理文件中,这会导致对应用程序的现有代码做有风险的变更。另外一个替代方法是重写整个应用程序。很多时候,这些代码已经没有任何变更地工作了数年甚至数十年,所以代码变更影响越小越好。
为了避免这些问题,并让开发者能够将现有的、非关系数据映射成关系数据,FairCom公司的 c-treeACE数据库引入了对多记录类型(MRT)表的支持。MRT表支持为那些无法融入到单个SQL模式的数据提供了一种处理方法。MRT表是为了和最开始就是以非关系表保存的已有数据一起使用的。虽然很怀疑现在是否还有某个开发者会基于MRT表来创建一个全新的应用,但是任何与前面所说的传统应用打交道的开发者将会发现这是非常有价值的。如将前面提到的批处理程序设想为某种类型的ETL(抽取,转换,加载),则可将MRT表设想为某种类型的“虚”ETL(抽取,转换,创建一组可通过SQL实时访问的虚拟表)。FairCom的技术的魔力在于转换是即时进行的,而不用将数据拷贝到外部表格,所以数据一直是最新的。
本文在一个例子中展示了如何使用MRT表来实时地为非关系c-tree数据提供NoSQL和SQL的并发访问。其目的是为了这些应用程序开发者和应用程序管理员,他们正在为c-treeACE数据、或那些可以容易迁移到c-treeACE的应用程序提供SQL访问而寻找一个简单机制。
c-treeACE
在深入细节之前,我们先简要介绍下FairCom的c-treeACE数据库,c-treeACE是一个高性能的NoSQL和SQL数据库引擎,其在NoSQL API层次上提供了很多传统数据库的概念,比如通过事务处理所提供的全部ACID属性、前滚、回滚、记录和键级锁、死锁检测、热备份、自动灾难恢复等。因为这些数据基本概念是在NoSQL层次实现的, c-treeACE使得可以从各种各样的面向NoSQL记录的API和关系API来并发访问同样的数据文件。
下图展示了c-treeACE所支持的API和框架:
图2- NoSQL API包括ISAM API、一个名为JTDB的在ISAM之上的本地Java实现、一个名为c-treeDB for .NET在ISAM之上的.NET实现以及其他几个。而SQL API则包括SQL、JDBC、ODBC、PHP、ADO.NET等。
就像我们将在下面探讨的,从NoSQL和SQL并发访问数据的能力给那些不是以关系格式保存的数据的访问带来了好处。在本文中,我们将会使用标准的SQL命令来访问数据,并用这种方式演示如何使用SQL访问非关系数据。在实际中,我们可以用任何使用工业标准SQL的应用程序来访问这些数据。这也为优化数据访问以获取相对于只支持SQL的数据引擎的性能改善提供了机会,不过这会是另外一篇文章的主题。
在本文中所提供的示例代码使用了NoSQL c-treeDB C API,该API在ISAM API之上提供一个了更高的层次,其通过提供用于缓冲管理的例程、会话建立、将应用数据和索引文件组合到一个数据库中而节省了程序员的时间和努力。经过三十多年的努力,FairCom已经为垂直市场应用、企业范围应用、全球范围的嵌入式处理提供了这类底层数据库技术。c-treeACE是用于高速数据索引和抽取的引擎级的解决方案。从苛刻的Visa的信用卡事务处理工作到NASA的外太空应用的各种各样事物都使用了该技术。
多记录类型支持
在c-treeACE中,当记录的结构根据某些准则随记录而不同时,可以使用一个MRT表。在我们即将看到的例子中,数据模拟了在前面所说过的传统ERP应用类型,在这些数据中有四种互不关联的NoSQL记录被组合存放到一个表中(tutorial_host):
- 顾客
- 订单
- 库存项
- 订单项
含有这些数据的实际表(tutorial_host)被称为“主(host)”。单个主表可以拥有多个MRT表。每种记录类型对应一张MRT表。在单个MRT表中的所有记录将会使用同样的记录模式。
我们会定义一些规则来决定每张MRT表会包含主表中的哪些记录。在本例中,主表拥有一个名为rectype的字段来表明在每条记录中的数据类型,所以我们的规则基于该字段。下面的图表表示了我们如何使用rectype字段来将四种记录结构分解到各自的虚拟MRT表中:
- CustMast(rectype=C)-属于顾客主表的记录
- CustOrder(rectype=O)-属于顾客订单表的记录
- ItemMast(rectype=M)-属于库存项主表的记录
- OrderItem(rectype=I)-属于已卖给每个顾客的商品表的记录
请记住,这种映射工作是在访问这些记录时由c-tree实时处理的。不用将这些数据拷贝至分开的SQL表中。对于访问MRT表的应用程序来说,这些表看起来就像真实的SQL表一样,但实际上它们按需生成的“虚拟的”表。
主表要为所有的记录定义一个通用的模式,虽然该模式可能只是描述了记录的第一部分:所有记录都拥有的那部分。在本例中,rectype字段是我们的通用的字段。一个基于该字段的筛选器识别属于某个MRT表的记录。c-treeDB API为筛选、MRT表创建、定义各种模式提供了方法来定义规则。
使用c-treeDB API像普通表一样来操作一张MRT表。c-treeDB的ctdbCreateMRTTTable()函数的行为很像普通的ctdbCreateTable()函数;然而,它创建的是一张MRT表。一旦已定义好MRT表,就可以像在普通表上一样在该表上使用其它的c-treDB命令(ctdbWriteRecord(),ctdbFirstRecord(),ctdbGetFieldAsString()等) 。当我们列出某个c-treeDB字典的表时,就像下面的屏幕截图中所显示的一样,会在一起列出MRT表和常规表。
记录结构示例
我们来研究下示例,在本例中有四种不同的记录结构全都存储在单个非关系的名为tutorial_host.dat的物理文件中。当你看到下面的这些截屏时,会发现有五张表。tutorial_host是唯一的物理表。其他这些在“Tables”标题下(custmast、custorder、itemmast和orderitem)列出的实体表示了所有四种不同记录结构的虚拟表,这四种都是储存在tutorial_host表中的。这些虚拟表都是由c-treeACE SQL引擎使用MRT支持来即时创建的。在进入细节之前,我们更进一步地研究一下示例程序:
首先是Customer Master记录结构(在截屏中标记为“custmast”),其包含所有rectype字段值为“C”的记录:
接下来是记录类型Custom Order(“custordr”),其包含了所有rectype字段值为“O”的记录:
然后是记录类型Item Master(“itemmast”),其包含了所有rectype字段值为“M”的记录:
最后是记录类型Order Item(“orderitem”),其包含了所有rectype字段值为“I”的记录:
为了说明我们的观点,所有的四种不同的记录类型,包括总共14条记录,都存储在名为tutorial_host.dat的单个物理文件中,如下面的本地磁盘驱动器截屏所示:
在本文中所使用的像c-treeACE ISAM API和c-treeDB C API这样的NoSQL API 能很容易地应付这类编码实践,还可避免对已有应用程序代码的风险变更。因为这些已有应用程序代码已经没有任何变更地工作了数年或甚至数十年,所以应尽可能地避免做出破坏性的代码变更。就像早先提到的,替代的方法通常是将数据导出到外部SQL表,但这种做法也有自己的问题。
为了避免这些问题,这些在单个文件中含有这类多记录结构的c-treeACE的用户可以使用MRT表支持来为这类文件提供SQL API访问。
示例
现在让我们看一看一个即时的MRT表示例。所附代码遵循所有的c-treeACE教程的标准格式:
- 初始化—执行登录到c-treeACE SQL服务器这一最低要求。
- 定义—创建并打开名为“tutorial_host.dat”的含有四种MRT表的(如不同的记录类型)c-treeACE数据文件。
- 管理—操作这些表并执行一个简单查询。这里就是我们所看到的MRT表起作用的地方。我们将会先使用面向记录的命令来访问数据,然后就像我们已经有了四张不同的表一样使用SQL来访问同样的数据。
- 完成—处理关闭表和释放相关内存这些杂务工作。
初始化
通过使用下面这些c-treeACE CTDB函数来执行登录到c-treeACE服务器这一最低要求:
- ctdbAllocSession(CTSESSION_CTDB)—初始化一个新的会话句柄,设置默认属性。
- ctdbAllocDatabase(hSession)—分配内存并初始化一个新的数据库句柄。
- ctdbLogon(hSession,”FAIRCOMS”,””,””)--登录到c-treeACE数据库服务器。“hSession”是会话句柄,“FAIRCOMS”是c-treeACE服务器名,第三和第四个参数分别是用户ID和用户密码。””表示使用默认的用户ID ADMIN,并使用默认的用户密码ADMIN。
- ctdbConnect(hDatabase,”ctreeSQL”)—连接到数据库,“hDatabase”是数据库句柄,而“ctreeSQL”则是数据库名。
定义
接下来我们定义表。因为这是一个自包含的示例,我们将会创建表并操作这些表。对于一个传统的应用程序,因为可能已经存在了数据,所以我们仍然会要定义MRT表,但是我们不一定会要去操作它们。
为了做到这点,我们尝试打开这些表来看看它们是否已经存在。如果不存在,我们创建并打开它们。下面的示例代码提供了一些高层次的函数来创建将会用到的表:
Create_HOST_Table(); Create_CustomerMaster_Table(); Create_CustomerOrders_Table(); Create_OrderItems_Table(); Create_ItemMaster_Table();
这些函数使用了标准的c-tree函数ctdbOpenTable()来试图打开这些表。如果某个表不存在,它会使用ctdbCreateMRTTable()函数来创建该表。该函数与标准的ctdbCreateTable()函数很相似,除了该函数是基于主(父)表来创建一个MRT表。一旦创建了,一个MRT表就表现得像一个常规的c-treeDB表。
- ctdbCreateMRTTable(CTHANDLE Handle, pTEXT VTableName, pTEXT ParentName, CTCREATE_MODE CreateMode, pTEXT filter)—使用下面的参数来基于一个主表创建一个MRT表:
- Handle:MRT表句柄
- VTableName:MRT表名
- ParentName:主(父)表名
- CreateMode:虚拟表创建模式
- Filter:应用于主表的筛选器,用来识别属于该MRT表的记录
我们然后使用下面的函数来打开表:
- ctdbOpenTable(CTHANDLE Handle,pTEXT TableName,CTOPEN_MODE OpenMode)—打开指定名的已存在表:
- Handle:表句柄
- TableName:表名
- OpenMode:表打开模式
管理
创建了表后,我们操作这些表并执行一个简单的查询。本节调用本例中所提供的函数来将数据添加至这些表:
Add_CustomerMaster_Records(); Add_CustomerOrders_Records(); Add_OrderItems_Records(); Add_ItemMaster_Records()
上面那些函数使用了标准的ctdbWriteRecord函数来将数据存放到这些表中。
- ctdbWriteRecord()—添加一条新记录或是更新一条已有记录。
- Handle:记录句柄
面向记录访问数据
我们已经包含了一个面向记录访问数据的例子,其模拟了在现有的基于NoSQL的应用程序中所发现的编程类型。为了演示,我们使用面向记录的函数来执行一个对那些数据的查询:
- ctdbFirstRecord()—获取表中的第一条记录。 记录的顺序是由创建表时所定义个某一个索引所决定的。
- Handle:记录句柄。
- ctdbGetFieldAsString()—获取一个字段的CTSTRING类型值。
- Handle:记录句柄。
- FieldNbr:要获取的字段的编号。为了获取给定记录句柄的字段编号,可以使用ctdbGetFieldNumberByName()。
- pValue: 指向字符串值的指针。
- SIZE:pValue的字节数大小。
- ctdbGetFieldAsSigned()—获取一个字段的有符号类型值。
- Handle:记录句柄。
- FieldNbr:要获取的字段的编号。为了获取给定记录句柄的字段编号,可以使用ctdbGetFieldNumberByName()。
- pValue:指向有符号数值的指针。
SQL访问同样数据
因为我们已经为自己的数据定义好了MRT表,所以现在可以使用标准的SQL查询来访问同样的数据。
请使用下面的SQL查询来检查表的内容,:
select * from custmast; select * from custordr; select * from itemmast; select * from ordritem;
这四条查询生成了在本文开始所见到的那些记录结构截屏。
按顾客列出所有的订单:
SELECT c.cm_custname, o.* FROM custmast c INNER JOIN custordr o ON c.cm_custnumb=o.co_custnumb;
列出顾客购买的所有商品:
SELECT c.cm_custname, o.co_ordrdate,oi.oi_quantity,i.im_itempric,i.im_itemdesc FROM custmast c INNER JOIN custordr o ON c.cm_custnumb=o.co_custnumb INNER JOIN ordritem oi ON oi.oi_ordrnumb = o.co_ordrnumb INNER JOIN itemmast i ON i.im_itemnumb=oi.oi_itemnumb;
列出顾客的总采购额:
SELECT c.cm_custname AS name, SUM(oi.oi_quantity * i.im_itempric) AS total FROM custmast c INNER JOIN custordr o ON c.cm_custnumb=o.co_custnumb INNER JOIN ordritem oi ON oi.oi_ordrnumb = o.co_ordrnumb INNER JOIN itemmast i ON i.im_itemnumb=oi.oi_itemnumb GROUP BY cm_custname;
完成
我们以处理关闭表并释放任何关联的内存这些杂务事来结束。该过程使用了包括ctdbFreeRecord()、ctdbFreeTable()和ctdbFreeSession在内的标准函数。
结论
在本文中,我们已经看到如何使用c-treeACE的多记录类型支持来为c-treeACE数据同时提供NoSQL和SQL访问,这类数据在单个表中混有多个模式,从而为这些系统能使用今天的现代化的设备和功能打开了一道崭新的门,与此同时不用对这些系统做出重要的应用程序变更。你可以下载一个全功能版本的c-treeACE Express数据库,并可以通过 support@faircom.com联系FairCom的支持来获取一份上面示例程序的工作副本。
作者简介
作为FairCom的工程副总裁,Randal Hoff帮助设定了c-treeACE的产品线技术方向,他和FairCom的工程师团队共同工作来创造高性能的、可靠的、经济的跨平台的数据库技术以满足数据库开发者的迫切需要,这些开发者涵盖企业、ISV和嵌入式设备市场。他跨越25年的技术生涯还包括在FairCom和通用电气的逐步担当起的种种负责职务。Hoff持有密苏里州,哥伦比亚大学的计算机工程和电气工程科学学士学位。
查看英文原文:How to Provide SQL Access to NoSQL Type Data using Multi-Record Type
感谢杨赛对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。