上一篇简单描述了一下SequoiaDB的简单CRUD操作,本篇将讲述一下稍微高级点的功能。

部署在我机器上的集群环境,在经过创建名字为"foo"的cs,创建名字为"bar"的cl,以及插入一些数据之后,并没有删除掉,因此在本篇中会继续使用。

首先,我们先看看,在SequoiaDB的安装目录中的database目录里面,有那些文件:

~$ ls /opt/sequoiadb/database/data/11850

我们会发现有几个文件:foo.1.idxfoo.1.data

正好是我们创建的cs的名字。是不是巧合呢?

验证一下,依次做以下操作:

  • 启动两个终端:终端1,终端2;
  • 在终端1中,进入SequoiaDB的shell执行环境;
  • 连接上数据库。

以上我不再写出操作,读者可以自己操作一下,有利于熟悉常用操作。

先删除Collection Space,执行:

> db.dropCS("foo")

然后在终端2下,查看一下文件:

~$ ls /opt/sequoiadb/database/data/11850

这个时候,我们发现foo.1.datafoo.1.idx文件没有了。

再回到终端1,重新创建名为“foo”的cs:db.createCS("foo")

切换到终端2,再次查看文件:

~$ ls /opt/sequoiadb/database/data/11850

这个时候,foo.1.datafoo.1.idx文件又有了。

因此,我们基本可以确定,一个cs对应一个.data文件和一个.idx文件;cl是data文件下的逻辑概念,相当于是关系数据库中的table。故在操作文件的时候,请谨慎,不是不得已,不要动这些*.data*.idx 文件

上面算是一点积累,准备进入本篇的正题:

因为上面的操作删除掉了cs,然后重新创建了名为"foo"的cs(虽然名字一样,但是删除过一次,里面的内容已经没有了)。所以再次创建名为"bar"的cl。

再切换到终端1,创建cl:

> db.foo.createCL("bar")

先构造一些数据:

> docs = [
... {"name":"Milky", "age":24},
... {"name":"Jim", "age":23, "ip":"192.168.1.131"},
... {"name":"Tyle", "age":24, "phone":"10086"},
... {"name":"Tony","age":33 } ]

插入数据:

> db.foo.bar.insert( docs )

一、创建索引

  有时候数据很多,但是想尽快查询到特定的数据,这个时候,就需要用到索引了。

  在简历索引之前,先查询一下数据,并查看一下数据是通过普通扫描查询到的,还是通过索引扫描查询到的:

> db.foo.bar.find({"age": 24}).explain()

  结果是:

{
"Name": "foo.bar",
"ScanType": "tbscan",
"IndexName": "",
"UseExtSort": false,
"NodeName": "Milky:11860",
"ReturnNum": 0,
"ElapsedTime": 0.000003,
"IndexRead": 0,
"DataRead": 0,
"UserCPU": 0,
"SysCPU": 0
}

  然后创建一个索引:

> db.foo.bar.createIndex("ageIndex", {"age":1})

  然后我们再执行:

> db.foo.bar.find({"age": 24}).explain()

  结果是:

{
"Name": "foo.bar",
"ScanType": "ixscan",
"IndexName": "ageIndex",
"UseExtSort": false,
"NodeName": "Milky:11860",
"ReturnNum": 0,
"ElapsedTime": 0.000003,
"IndexRead": 0,
"DataRead": 0,
"UserCPU": 0,
"SysCPU": 0
}

  由于数据量太小,查询耗时对比都不太明显。但是在两次对比结果的“ScanType”字段的值中能看出一个走的tbscan,一个走的ixscan。如果有时间,可以试着插入万条级别的数据,再次试试,耗时的对比会比较明显。

二、删除索引

  这个就比较简单了。稍微演示一下:知道要删除的索引的名字是“ageIndex”,调用接口:

> db.foo.bar.dropIndex("ageIndex")

  再执行:

> db.foo.bar.find({"age": 24}).explain()

  结果是:

{
"Name": "foo.bar",
"ScanType": "tbscan",
"IndexName": "",
"UseExtSort": false,
"NodeName": "Milky:11860",
"ReturnNum": 0,
"ElapsedTime": 0.000003,
"IndexRead": 0,
"DataRead": 0,
"UserCPU": 0,
"SysCPU": 0
}

  此时的ScanType字段的值变成了“tbscan”,说明这次的查询,没有走索引:索引删除成功。

  一个好的索引,对dba来说,是一项牛逼的技能。关于如何创建一个高效的索引,已经超出本篇的范围,在此不做描述,请自行google学习。

三、记录条数计数

  这个操作也是一个很简单明了的操作。

  在以上操作的基础上,执行:

> db.foo.bar.count()

  返回:

4

  说明这个时候,cl中有四条数据(cl中的确是4条数据)。

四、聚集

  在SQL中,聚集是一个简单的语法。而在没有SQL语句的NoSQL中,就是aggregate来大显神威了。

  由于这块内容涉及到大量的匹配符等,我采用SequoiaDB官网给出的例子来演示:

  先构造数据:

> tom = {
... "no":1000,
... "score":80,
... "interest":["basketball", "football"],
... "major":"计算机科学与技术",
... "dep":"计算机学院",
... "info":{
... "name":"Tom",
... "age":25,
... "gender":"男"
... }
... } > sam = {
... "no":1000,
... "score":80,
... "interest":["music"],
... "major":"软件工程",
... "dep":"计算机学院",
... "info":{
... "name":"Sam",
... "age":22,
... "gender":"男"
... }
... }
> db.foo.bar.insert(tom)
> db.foo.bar.insert(sam)

  然后执行:

> db.foo.bar.aggregate({"$match":{"no":1000}}, {"$group":{"_id":"$major", "Major":{"$first":"$major"}, "avg_age":{"$avg":"$info.age"}}})

  输出结果:

{
"Major": "软件工程",
"avg_age": 22
}
{
"Major": "计算机科学与技术",
"avg_age": 25
}

  详细请参考SequoiaDB官网信息中心>>参考手册>>SequoiaDB Javascript方法>>SdbCollection>>db.collectionspace.collection.aggregate

  PS:有朋友私下问我,官网点进去,很难找到对应的位置。因为SequoiaDB官网的文档无法定位到确切的位置,所以只能索引到信息中心位置。很多需要慢慢看,如果熟悉一些数据库常用术语的话,定位会快一点。可以去SequoiaDB的社区吐槽哈,他们的社区地址是:SequoiaDB社区

五、切分

  对于每一条,有热数据与冷数据的区分。对于热数据,需要频繁访问;对于冷数据,可能访问的几率会比较小。因此,热数据所在的磁盘性能可能会更好一点。如果把冷数据和热数据都存放在性能好的磁盘上,会占据磁盘空间,由于不常访问,浪费了资源。因此热数据和冷数据通常要分开存放。

  这就是数据切分的由来了。

  SequoiaDB的数据切分需要用到两个数据组,把冷的数据,切分到另外一个组上。

  回顾第一篇部署集群环境的时候,已经就创建了一个数据组。在此,还需要创建另外一个组,组名叫“colddatagroup”。

  部署步骤,请参考 SequoiaDB系列之一:SequoiaDB的安装、部署

  先附切分原理图一张:

  SequoiaDB 系列之三   :SequoiaDB的高级功能-LMLPHP

  现在,要创建一个新的名为"total"的cs,并在这个cs上创建一个名为"age"的cl,这个cl的分区键类型是"age",分区类型是"range"的:

> db.createCS("total")
localhost:11810.total
Takes 0.193840s.
> db.total.createCL("age", {"ShardingKey":{"age":1}, "ShardingType":"range"})
localhost:11810.total.hot
Takes 3.288509s.

  往这个cl中插入几条"age"的值不同的数据:

> docs = [
... {"age":20},
... {"age":21},
... {"age":22},
... {"age":23},
... {"age":50},
... {"age":60},
... {"age":62},
... {"age":68},
... {"age":79},
... {"age":85},
... {"age":90} ]
[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]
Takes 0.42086s.

  插入数据:

> db.total.age.insert(docs)
Takes 0.2119s.

  然后定义一个规则,65岁之后的人,咱不管了,当冷数据处理。要把 age大于或等于65的,切分colddatagroup组上。输入:

> db.total.age.split("datagroup", "colddatagroup", {"age":65}, {"age":100})

  数据量很大的时候,这个操作耗时会有点长,耐心等待即可。

  等待操作完成,我们需要检验一下是否真的切分了。

  重新建立一个连接,这个连接比较特殊,因为这个连接是直接连接到某个数据节点上(还记得上一篇有一处提到的吗?生产环境下,不建议有这样操作)。

> node = new Sdb("Milky", 18800)

  这个节点是colddatagroup数据组上的节点,也是我们切分之后,存放age大于或等于65的数据的数据组中的数据节点。

  查看一下这个节点上有哪些cl:

> node.listCollections()

  结果显示的cl,名字整好是datagroup数据组上,用于切分的源数据组的collection的名字。

  查询一下:

> node.total.age.find()

  结果输出:

{
"_id": {
"$oid": "54ba9abe74b1303560000044"
},
"age": 68
}
{
"_id": {
"$oid": "54ba9abe74b1303560000045"
},
"age": 79
}
{
"_id": {
"$oid": "54ba9ca374b1303560000048"
},
"age": 80
}
{
"_id": {
"$oid": "54ba9abe74b1303560000046"
},
"age": 85
}
{
"_id": {
"$oid": "54ba9abe74b1303560000047"
},
"age": 90
}

  这些数据正是age字段的值,大于或者65的记录。

  如果还有疑问,可以再次直接连接到datagroup的数据组中的节点上,查询源数据组中的记录,检查是否其中的数据,age字段的值是否小于65。

至此,本篇也到了末尾结束部分,感谢您的耐心阅读!

下一篇,将进入本系列的重点部分,简析SequoiaDB的架构。敬请期待!

=====>THE END<=====

05-11 17:44