引言
随着 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 )关注我们,并与我们的编辑和其他读者朋友交流。
评论