NoSQL 教程
几个月前,我与 NoSQL 的创建者 Carlo Strozzi 讨论了数据库。
我必须承认,我是 SQL 的粉丝!无论使用哪个平台或数据库引擎,使用相同的语言都很棒。他强调,大多数 SQL 引擎缺乏灵活性,并且由于其多平台环境(例如 Oracle、DB2、Informix 等),浪费系统资源(内存和磁盘空间)。
他建议我看看启发他的白皮书:“UNIX Shell 作为第四代语言”,作者是 Evan Schaffer (evan@rsw.com) 和 Mike Wolf (wolf@hyperion.com)。
引用上述论文
... 几乎所有[数据库系统]都是软件监狱,您必须进入其中并抛弃 UNIX 的强大功能。[...] 由此产生的数据库系统是大型、复杂的程序,会降低整体系统性能,尤其是在多用户环境中运行时。[...] UNIX 提供了数百个程序,这些程序可以管道连接在一起,轻松执行几乎任何可以想象到的功能。没有什么能比得上 UNIX 标准附带的功能。
UNIX 文件结构是有史以来最快、最容易获得的数据库引擎:目录可以看作是编目,表可以看作是纯 ASCII 文件。命令是常见的 UNIX 实用程序,例如 grep、sed 和 awk。不应该重新发明任何东西。
NoSQL 的诞生就考虑了这些想法:充分利用 UNIX 系统,使用一些命令将各种标准工具粘合在一起。虽然 NoSQL 是一个很好的数据库系统,但这并不是解决所有问题的灵丹妙药。如果您必须处理一个 10 GB 的表,该表必须每秒从各种客户端更新,NoSQL 不适合您,因为它在非常大的表上缺乏性能,并且在频繁更新时您必须实时进行。对于这种情况,我建议您使用更强大的解决方案,例如基于 Oracle、DB2 或此类软件包在 Linux 集群、AS/400 或大型机上。
但是,如果您的网站包含大量信息并且读取操作多于写入操作,您会惊讶于它的速度有多快。NoSQL(发音为 noseequel,正如作者建议的那样)的大部分代码都来自 RAND Organization 开发的 RDB 数据库,但为了完成更多任务,构建了更多命令。
最新的 NoSQL 源代码可以在 ftp://ftp.linux.it/pub/database/NoSQL 找到,但也提供了 RPM 和 Debian 软件包。在撰写本文时,最新的稳定版本是 2.1.3。
只需在方便的目录(例如 $HOME/src)中使用命令 tar -xzvvf nosql-2.1.3.tar.gz 解压缩源代码,您将在 nosql-2.1.3 子目录中获得所有代码。进入上述子目录并执行以下操作
./configure make make install
该软件将引擎放入 /usr/local/lib/nosql,将其文档放入 /usr/local/doc/nosql,并将符号链接 /usr/local/bin/nosql 指向真正的可执行文件 (/usr/local/lib/nosql/sh/nosql)。您可以通过调用 ./configure --prefix=/usr 来更改目录前缀(例如,/usr 而不是 /usr/local)。
您应该将示例配置文件复制到 $NSQLIB 目录(即 /usr/local/lib/nosql)。这不是必需的,但它对于通过配置文件而不是变量更改某些参数很有用。命令
cp nosql.conf.sample /usr/local/lib/nosql/nosql.conf chmod 0664 /usr/local/lib/nosql/nosql.conf
将使用正确的权限复制它。您可以选择创建一个带有 0664 权限的 $HOME/.nosql.conf 来拥有个人 NoSQL 配置文件。
虽然 NoSQL 安装非常简单,但我建议您阅读 INSTALL 文件:作者给出了一些很好的提示。
现在软件包已安装,让我们通过一个示例开始熟悉 NoSQL 命令。
我们是通常的 Acme Tools Inc. 公司,为 Toonies 土地供应物资。我们想跟踪我们的客户,因此我们应该创建一个第一个表,其中列出一些客户详细信息(例如代码、电话、传真、电子邮件等...)。从头开始创建表的最佳方法是通过模板文件。
模板文件包含表的列名和关联的可选注释,它们之间用制表符和/或空格分隔。注释也可以用通常的井号 (#) 指定为行的第一个字符。下面的文件 customer.tpl 是我们客户表的模板。
# Acme Tools, Inc. # Customers table ####################### CODE Code number NAME Name/Surname PHONE Phone no EMAIL E-mail
大多数 NoSQL 命令从 STDIN 读取并写入 STDOUT。maketable 命令从模板构建表,就是其中之一。发出命令
nosql maketable < customer.tpl我们将在 STDOUT 上获得表头
CODE NAME PHONE EMAIL ---- ---- ----- -----太好了,但我们应该将其保存在文件中。我们可以简单地将命令输出重定向到一个文件,例如,
nosql maketable < customer.tpl > customer.rdb但这并不是正确的方法。write 命令在这种情况下可能很有用,因为它从 STDIN 读取表(在这种情况下是一个简单的标头)并写入文件,检查数据完整性。
结果命令将是
nosql maketable < customer.tpl | nosql write -s customer.rdb.
write 运算符中的 -s 开关抑制 STDOUT,例如,nosql write,类似于 UNIX 实用程序 tee,除非指定了 -s,否则会同时写入文件和 STDOUT。
请注意,因为 write 命令不对输出表执行任何锁定:nosql lock table 和 nosql unlock table 必须用于此目的。
现在让我们使用命令添加我们的第一个客户
nosql edit customer.rdb
默认编辑器是 vi 命令,但您可以使用您喜欢的编辑器更改 EDITOR 环境变量。向用户显示的屏幕如下
CODE NAME PHONE EMAIL只需填写字段信息,记住用制表符分隔字段名称和值。不要删除第一个和最后一个空行,这不是错误:这是 NoSQL 处理列表的方式。但我更喜欢让您在本文后面发现这个小功能。
CODE ACM001 NAME Bugs Bunny PHONE 1 EMAIL bugs.bunny@looneys.com现在我们已经填写完表单,只需写入它(ESC 然后 :wq!),命令将检查格式是否正确并将其写入磁盘。哇,我们有了一个真正的表和真实的数据!
因为我们很好奇,所以我们将查看磁盘上的真实文件。
CODE NAME PHONE EMAIL ACM001 Bugs Bunny 1
首先,重要的是要注意所有列都是制表符分隔的事实:当您希望某些外部程序更新表时,请牢记这一点,否则您将破坏表的完整性。
第一行称为 标题行,包含列名;第二行是 虚线行,它将标题行与正文分开:两者都称为 表头。其余部分称为 表体,包含实际数据。
已构建许多命令来显示这些部分,它们只是对普通 UNIX 实用程序的调用
nosql body:显示表体(与:tail +3 > table 相同)
nosql dashline:显示表虚线(与:sed -n 2p < table 相同)
nosql header:显示完整的表头(与:head -2 < table 相同)
nosql headline:显示表标题行(与:head -1 < table 相同)
nosql see:将 TAB 字符显示为 ^I,将 换行符 显示为 $,从而更容易查看损坏的表出了什么问题(与:cat -vte < table 相同)
再次,这表明 UNIX 操作系统本身有多么强大,以及对于 NoSQL 等附加软件包来说,无需重新发明轮子即可利用这种能力是多么方便。
一种有趣的填充表的方法是使用环境变量。您可以通过任何方式导出变量,例如,在 CGI 环境中使用 UNCGI 或命名为列名,并使用所需的值,如下所示
export CODE="ACM002" export NAME="Daffy Duck" export PHONE="1-800-COOK-ME" export EMAIL="dduck@looneys.com"
然后发出命令
nosql lock customer.rdb; env | nosql shelltotable |\ nosql column CODE NAME PHONE EMAIL |\ nosql merge CODE NAME PHONE EMAIL customer.rdb |\ nosql write -s customer.rdb; nosql unlock customer.rdb工作就完成了——有点神秘?是的,但这就是 NoSQL 的强大之处:一切都可以在单个 shell 命令中完成。让我们解释一下
nosql lock customer.rdb:这会锁定表,并确保在我们写入表的同时,没有其他人可以写入表。
env:打印环境变量。
nosql shelltotable:从管道读取所有变量,并将包含所有值的单条记录表写入 STDOUT。
nosql column CODE NAME PHONE EMAIL:从管道读取包含环境变量的 NoSQL 表,并按该顺序选择列 CODE、NAME、PHONE 和 EMAIL,然后写入 STDOUT。
nosql merge CODE NAME PHONE EMAIL customer.rdb:读取两个合并表,一个来自管道 (STDIN),另一个来自文件,并将合并后的表写入 stdout。结果表有两条记录:现有记录和从上述过程提取的新记录。
nosql write -s customer.rdb:读取结果表(从上述命令合并)并将其作为 customer.rdb 写入磁盘。我们已经解释了开关 -s 的含义。
nosql unlock customer.rdb:解锁表。
CODE NAME PHONE EMAIL ------ ---------------- --------------- ---------------------- ACM001 Bugs Bunny 1-800-CATCH-ME bugs.bunny@looneys.com ACM002 Daffy Duck 1-800-COOK-ME dduck@looneys.com
NoSQL 可以处理不同方式的数据,称为列表格式。一个示例表可能是
CODE ACM003 NAME Bart Simpson PHONE 1-555-5432-321 EMAIL bart@springfield.org CODE ACM004 NAME Wiley The Coyote" PHONE 1-800-ILLGETIT EMAIL wiley@looneys.com
是的,您是对的!这与 edit 命令显示数据的方式相同。虽然列表表的性能根本不好,但在我看来,它们是将新数据插入表中的好方法。创建一个可以输出这种格式的程序很方便。示例显示在 清单 1 中。
好吧,这不是一个“最先进的”shell 程序,但这个例子可能表明,使用 每种 语言,甚至是 shell,完成整个操作都很容易。
关于上面的代码,我想强调几件事。列表如何与真实表合并?正如您可能在查看管道时猜到的那样,命令 listtotable 为您完成了这项工作,它将列表格式转换为表格格式。反向命令 tabletolist 也存在。
请注意开头和结尾的换行符,以及 print 语句中字段名称和值之间的制表符:这些是创建正确列表结构所必需的。
列表结构以及表结构在 NoSQL 参考手册的第 2 章中有详细记录,您可以在 /usr/local/doc/nosql/doc 中找到该手册。
现在,让我们来看一个商业案例?完整的目录是使用 NoSQL 表(请参阅下面的 catalog.rdb 文件)创建的,并在 Web 上动态发布。每两天,我们都会收到客户的订单,这些订单通常是他们使用 Excel 创建的,并应我们的要求导出为逗号分隔文件。
PRID DESC PRICE ------ ------------------------- ------ PRD001 Acme glue for RoadRunners 30.00 PRD002 Acme TNT 150.00 PRD003 Carrots 5.00 PRD004 Acme toolbox 75.00 The file (sample_order.txt below) we receive has the following format: requester's unique code, Product ID, Quantity.
ACM004,PRD001,5 ACM004,PRD002,30 ACM004,PRD004,1
现在从 shell 或命令行我们运行
export TMPFILE=`mktemp -q /tmp/$0.XXXXXX` ; cat sample_order.txt | \ perl -e 'print "CODE\tPROD\tQTY\n"; print "----\t----\t---\n"; \ while( catalog.rdb | \ nosql addcol SUBTOTAL | nosql compute 'SUBTOTAL = QTY*PRICE' > $TMPFILE ; \ echo "Please bill to:" ; echo "---------------" ; echo ""; cat $TMPFILE |\ nosql join -j CODE - customer.rdb | nosql column NAME PHONE EMAIL | \ nosql body | head -1 ; echo "";echo "" ; cat $TMPFILE | nosql rmcol CODE | \ nosql print -w; echo ""; echo -n "Total due: "; cat $TMPFILE | \ nosql subtotal -T SUBTOTAL | nosql body ; rm $TMPFILE我们的输出是
Please bill to: --------------- Wiley The Coyote 1-800-ILLGETIT wiley@looneys.com PROD QTY DESC PRICE SUBTOTAL ------ --- ------------------------- ------ -------- PRD001 5 Acme glue for RoadRunners 30.00 150 PRD002 30 Acme TNT 150.00 4500 PRD004 1 Acme toolbox 75.00 75 Total due: 4725此结果可以通过电子邮件发送给我们的物流人员。对于 五分钟的单个 shell 命令 来说,还不错,不是吗?
我知道这有点晦涩难懂,所以让我们仔细看看:解释分为四个部分,以便于阅读。我将排除 echo 命令,这些命令非常明显。
export TMPFILE=`mktemp -q /tmp/$0.XXXXXX`:导出环境变量 TMPFILE,其中包含运行时生成的临时文件。
cat sample_order.txt:获取我们收到的文件作为输入。
perl -e 'print "CODE\tPROD\tQTY\n"; print "-----\t----\t---\n"; while(<STDIN>) { s/,/\t/g; print };`:打印符合 NoSQL 要求的标头,由 CODE、PROD 和 QTY 组成。然后,它打印从 STDIN 收到的表,用制表符 (\t) 替换逗号 (,),以便获得正确的表结构。
nosql join -j PROD - catalog.rdb:使用 PROD(产品 ID)列连接从 STDIN 读取的表(- 字符)和 catalog.rdb。
nosql addcol SUBTOTAL:现在将列 SUBTOTAL 添加到结果表中。
nosql compute 'SUBTOTAL = QTY*PRICE' > $TMPFILE:通过将数量和价格列相乘来计算 SUBTOTAL 列。然后,它将结果表重定向到先前计算的临时文件。
cat $TMPFILE:读取从先前存储的临时文件写入的表。
nosql join -j CODE - customer.rdb:在 CODE 列上连接 STDIN 表(减号)和 customer.rdb 表。
nosql column NAME PHONE EMAIL:选择 NAME、PHONE 和 EMAIL 列。
nosql body:仅获取表内容,而不打印标题行。
head -1:仅打印一行;所有行都相同,因为发送文件(或订单)的人是同一个客户。
cat $TMPFILE:读取从先前存储的临时文件写入的表。''nosql rmcol CODE:从 STDIN 表中删除 CODE 列(此时它对我们没有用)。
nosql print -w:以简单但有用的形式打印结果。仅包含数字的列在右侧用空格对齐,而其他任何内容都在左侧对齐。-w 开关强制 print 命令适合终端窗口。
cat $TMPFILE:读取从先前存储的临时文件写入的表。
nosql subtotal -T SUBTOTAL:计算 SUBTOTAL 列的总和。此命令的结果是符合 NoSQL 要求的表。
nosql body:仅获取表内容,而不打印标题行。
rm $TMPFILE:删除之前创建的临时文件。此时使用的命令当然有更多选项。有关完整概述,请查看 NoSQL 软件包随附的文档。
在大量数据中搜索信息的最佳方法当然是索引,NoSQL 显然有自己的运算符来与它们交互。
假设自 Acme Inc. 成立以来,我们的客户数量大大增加,因此我们需要加快搜索速度。大多数时候,我们会在 CODE 列上查找,因此最好最后在它上面构建索引。
命令 nosql index customer.rdb CODE 将在 customer.rdb 表上针对 CODE 列创建索引。索引文件的命名方式是在它引用的表的基本名称后附加 x 和列名(用点分隔)。在我们的例子中,索引文件名是 customer.x.CODE。如果我们想更新索引而不重建它,我们应该运行
nosql index -update customer.x.CODE
我们系统上的 crontab 作业将通过运行命令来确保定期索引更新
cd /var/tables/acmeinc; nosql index -update在 nosql index -update 上不指定任何索引文件名将更新当前工作目录中的所有索引。
现在索引已构建,让我们尝试搜索。为了针对索引进行搜索,您应该创建一个小的键表作为 nosql search 命令的输入,例如
echo -e "CODE\n----\nACM004" | nosql search -ind customer.x.CODE
此命令将使用 customer.x.CODE 索引提取 CODE 列中的 ACM004 值。创建此键表可能看起来不太方便,但这确保了命令的最大重用。假设您从 table1 中提取了一些数据,您可以使用单个管道在 table2 上轻松搜索最后一个结果。尝试稍微思考一下,这根本不是一个坏主意。
如果您发现上次写入造成了灾难,并且您没有备份怎么办?您将永远不会拥有灾难发生之前表的最新副本。
修订控制系统 (RCS) 是可用的最佳配置管理工具之一,它可以用于多种类型文件的版本控制,包括表。例如,命令 nosql edit 将自动检出表以进行编辑,然后将新版本检入 RCS。其他命令可以通过使用显式命令(例如
co -p table | nosql row ... | nosql column ... | nosql print
或依靠 cat 命令自动处理与 RCS 的交互
nosql cat table | nosql row ... | nosql column ... | nosql print请注意,这会检出一个表,将其发送到 nosql row,然后发送到 nosql column,最后使用 nosql print 打印数据。一般来说,即使表已检入 RCS,也可以构建任何必要的命令序列来完成给定的任务。
现在您已经有 RCS 为您守望,如果现实生活中发生灾难,您可以轻松 rollback:命令
nosql rollback [-d datestring] tablename
将提取在所需时间更新的表。
无论您必须处理什么程序,迟早您都必须处理性能问题。我不是 NoSQL 数据库系统的开发人员,但我可以给您一些有用的建议。
首先,保持表小巧。不要将所有数据都保存在一个表中,这会浪费性能。使用最适合您环境的方法,尝试将表拆分为多个文件,并将它们组织到目录中。在我们的示例中,我们可以通过创建一个单独的“电话目录”(即 customer.rdb)来跟踪我们的客户,然后为每个订单状态(已收到、等待账单、已存档)创建一个目录,最后为每个客户创建一个表(文件名将是客户代码)。例如
/var/tables/acmeinc/ customer.rdb catalog.rdb received/ ACM001.rdb ACM003.rdb w4bill/ ACM002.rdb ACM003.rdb archive/ ACM001.rdb ACM002.rdb ACM003.rdb
如果您必须在一个大表中完成所有操作,并且必须经常更新它,那么如果它是索引文件,则有一个技巧可以帮助您:日志记录。
创建一个日志表,例如 customer.j,它与 customer.rdb 具有完全相同的标头,但仅包含我们要插入、从 customer.rdb 中删除或附加到 customer.rdb 的那些记录。customer.j 中的条目必须采用适合 nosql merge 命令的格式。
每当我们从 customer.rdb 获取数据时,我们都必须分三个逻辑步骤进行。第一步是在 bigtable 上使用 search 以利用索引。这将产生一个中间输出,然后将其合并到 customer.j 中,最终输出将再次进行原始查询语句。
对 customer.rdb 的任何更新都将使用 merge 命令的文档中描述的语法对 customer.j 进行(在那里您将找到如何将运算符使用到最佳水平)。您还必须确保在每次更新后,customer.j 都按其主键字段排序。例如,如果您的 customer.rdb 表中的 CODE 列上有一个索引,则应使用
echo -e "CODE\n----\nACM004" | nosql search -ind customer.x.CODE |\ nosql merge CODE NAME PHONE EMAIL customer.j | \ nosql row 'CODE=="ACM004"'
正如您所看到的,诀窍是
对 customer.rdb 执行索引搜索,以快速获得表的一个小得多(可能为空)的子集。
在查询期间,将第一个输出与 customer.j 动态合并。
对最终输出执行顺序后查询。
创建以下硬链接:ln /bin/ash /bin/ah
修改 AWK 以使其使用 /bin/ah 而不是 /bin/sh,并将修改后的 AWK 写入 /usr/local/bin/nsq-mawk:sed 's/\/bin\/sh/\/bin\/ah/g' /usr/bin/mawk\> /usr/local/bin/nsq-mawkchmod 755 /usr/local/bin/nsq-mawk
修改配置文件 (/usr/local/lib/nosql/nosql.conf 或 $HOME/.nosql.conf) 中的 NSQAWK 值,使其为 /usr/local/bin/nsq-mawk。这将加快您的查询速度!
在 Web 上使用 NoSQL 只需几秒钟。假设 Acme Tools Inc. 现在有一个网站,您希望您的客户在数据库中搜索他们待处理的订单。我们首先创建一个小的输入表单,文件 getname.html 显示在 清单 2 中,我们在其中询问客户姓名。在这个例子中我没有使用任何安全性,但至少在生产环境中应该询问密码。
由于我们不擅长图形,我们创建了一个小的模板 (result.html),可以轻松修改。此模板 result.html 显示在 清单 3 中。
在此模板中,某些关键字被 CGI 替换:我们的标准关键字以双井号 (##) 开头和结尾,例如 ##KEYWORD##。
模板的特殊部分,名为 stanza,将重复与查询行数相同的次数。stanza 以特殊注释开始和结束,这些注释将被 CGI 识别。
现在是编写 CGI 的时候了:清单 4,result.pl,是一个 perl 脚本,它应该根据输入名称执行查询,然后根据之前的模板创建结果页面。此 CGI 中使用的大多数查询都是之前示例中使用的查询,因此我们不会重复它们。只需查看主查询
@cusdata = `nosql cat $datafile | nosql join -j PROD - $ctlgfile | nosql addcol SUBTOTAL | nosql compute 'SUBTOTAL = QTY*PRICE' | nosql column PROD DESC QTY PRICE SUBTOTAL | nosql body`;
需要注意的是将包含查询行的数组:每行都包含制表符分隔的字段列表,如 NoSQL 表行规范。实际上,在 stanza 关键字替换中,使用了针对制表符的 split 函数
my ($prod, $desc, $qty, $price, $subtotal) = split(/\t/, $data);所有查询都使用反引号运行,反引号代替 exec() 和 system(),返回程序的 STDOUT。此输出可以在程序中使用变量重用。这样做的一个负面影响是安全性:您不能在污染模式下运行此程序(perl 命令行中的 -T 开关),但这可以通过一些技巧来避免,例如我使用的那些技巧。首先,您应该通过使用子字符串函数 ($cusname = substr($cusname, 0, 50)) 来避免缓冲区溢出,然后排除一些转义字符(例如 > 一旦查询执行完毕,并且我们拥有所有必要的值,我们就加载模板文件并将其与默认输入和模式搜索空间关联。模板文件(我们在其中将换行符和多个空格转换为单个空格)现在使用模式匹配分为三个部分:标头、正文(又名 stanza)和页脚。
/(.+)<\s*!--\s*here\s+starts\s+nosql\s+stanza\s*--->(.+)<\s*!--\s*here \s+ends\s+nosql\s+stanza\s*--\s*>(.+)/i ;此搜索将使用关键字 <!-- here starts nosql stanza --> 作为开头和 <--! here ends nosql stanza --> 作为结尾来识别模板中的 stanza。正如您所注意到的,这些是简单的 HTML 注释,因此可以由我们的图形专家轻松引入。开始注释之前的所有项目都被视为标头,而其余部分是页脚。
在进入正文中的关键字处理之前,我们将在标头和页脚中执行此操作,以便设置正确的客户名称和应付总额
$header =~ s/##NAME##/$cusname/; $footer =~ s/##TOTAL##/$total/;
最后一部分是 stanza 中的关键字替换。在这里,我们将原始变量($body)与临时变量($tmpbody)交换,以便保持第一个变量不变以供下一次循环使用。这里,字段使用我之前描述的方法拆分,然后替换模板文件中的关键字。当然,有成千上万种编写此类 CGI 的方法,无论是使用 Perl 还是其他语言。用您喜欢的语言编写一个,让您的想象力成为您的指南:数据库是纯 ASCII 文件,因此您可以根据需要处理它们,并且您将获得出色的结果。
有关 NoSQL 在 Web 上使用的真实示例,请查看 http://www.whoswho-sutter.com/、http://annunci-auto.repubblica.it/ 和 http://www.secondamano.it/(第一个是英文,而其他的是意大利文)。
在与 NoSQL 的主要开发人员 Strozzi 先生交谈时,他向我透露了一些关于 RDBMS 正在进行的开发的新闻。
您一眼就能看到的第一个次要修改是版本号:内核发布模式已引入,因此偶数是稳定版本,而奇数是不稳定版本(当前不稳定版本是 2.3.1)。
主要的修改是 rel 命令。它在更新/插入/删除之前检查表引用完整性,但不会采取任何操作:它只会告知您是否会发生任何破坏,因此您应该在程序中在执行任何表操作之前使用它。
其他次要增强功能是一些命令,例如 insert、delete、tabletoperl、perlencode 和 tabletom4,这些命令在编程环境以及命令行中都非常有用。目前,没有这些命令的官方参考,但源代码中的注释将很容易让您了解如何使用它们。
Carlo Strozzi 先生告诉我,下一个稳定版本 2.4.0 将于 1999 年 11 月左右发布。
NoSQL 是一个非常适合基于 Web 的应用程序的数据库系统,在这些应用程序中,读取操作远多于写入操作。我还建议将其用于全文搜索以及那些 ASCII 表可能很方便的应用程序。
有关更多信息,请访问官方网页 http://www.mi.linux.it/People/carlos/nosql/ 或通过发送消息至 noseequel@mi.linux.it 并以 Subject: 行中的词语 subscribe noseequel 订阅邮件列表。
我要感谢 NoSQL 的创建者 Carlo Strozzi 对我撰写本文的支持;Maurizio Sartori,他给了我一些提示;Giovanni Granata、Andrea Bortolan 以及所有鼓励我继续研究的人们。
本文中引用的所有清单都可以通过匿名下载文件 ftp.linuxjournal.com/pub/lj/listings/issue67/3294.tgz 获取。
