七个数据库,七个世界-第五章 MongoDB 第一天 1/2
首先
所以最终选择了MongoDB。为什么选择MongoDB呢?主要原因有以下几点:首先,MongoDB是一种NoSQL数据库,适用于海量数据的处理。其次,MongoDB具有极高的灵活性和扩展性,可以轻松应对未来的需求变化。最后,MongoDB具备强大的查询和索引功能,能够满足我们对日志数据的高效分析和利用需求。因此,我决定从本章开始学习MongoDB。
-
- Redis => ハッシュの探索が高速で、簡単に分散可能(らしい)。
-
- Riak => Redisと同じく、ハッシュの探索が高速。高可用性。Erlangで作られている。
-
- HBase => ビッグデータに強し。列指向ってのが難しそうだし、どうせならHadoopと一緒に使いたい。
- MongoDB => データへの問い合わせが簡単。ただ、速度がイマイチらしい。JSONで使用可能。
考虑到这些数据库并为了种种原因,决定采用MongoDB。因为它可以保存为JSON格式,所以我们可以考虑使用D3.js进行数据可视化。那么,让我们立刻开始吧。
简而言之
据说MongoDB于2009年发布。我感到惊讶,因为它很新颖。据说它结合了强大的关系型数据库查询能力和分布式数据存储(如Riak和HBase)的优点。这是个好消息。它没有结构化模式,所以可以根据数据模型进行增长和变更,并且具备水平扩展能力。
安装步骤
似乎可以通过brew轻松实现。
brew install mongodb
mkdir mongodb
mkdir /mongodb/db
mkdir /mongodb/log
顺便说一下,我创建了mongod用于保存数据的目录和用于日志的目录。现在需要注册这个操作目录的路径。
首先,启动Mongo服务,并使用–dbpath选项注册路径。
mongod --dbpath ~/Documents/mongodb/db
在这种状态下,访问http://localhost:27017,可以确认启动。要结束,可以按Ctrl+C。
通常情况下,可以在后台启动并保存日志,可以通过选项进行添加。
$ mongod --dbpath ~/Documents/mongodb/db --logpath ~/Documents/mongodb/log/mongodb.log &
$ ps u
$ cat log/mongodb.log
$ kill -KILL プロセスID
您可以通过使用”ps u”命令来确认是否已经启动。通过查看日志文件,可以确认日志文件”mongodb.log”中是否积累了日志信息。要停止以后台方式运行的进程,您只需使用”kill”命令,并输入您想要停止的进程的PID(进程ID)。–很多人喜欢使用”–fork”选项来启动,但我认为只需在命令后加上”&”符号更方便。
第一天: “CRUD和嵌套”
命令行很有趣
让我们创建一个名为”book”的新数据库。首先启动mongod,然后执行以下命令。
$ cd db
$ mongo book
MongoDB shell version: 2.6.7
connecting to: book
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
Server has startup warnings:
2015-09-17T22:20:25.531+0900 [initandlisten]
2015-09-17T22:20:25.531+0900 [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
一启动,首先在控制台输入help,然后会被告知输入help以获得帮助。
看了help之后就能知道,要查看数据库列表,需要输入show dbs。要切换数据库,需要使用use命令。
在Mongo中创建集合(类似Riak中的bucket),只需要向集合中添加第一条记录即可。Mongo是无模式的,所以不需要事先定义任何内容。实际上,在添加了值之前,book数据库也是不存在的。下面的代码是用来创建/插入集合towns的。
> db.towns.insert({
... name: "New York",
... papulation: 22200000,
... last_census: ISODate("2009-07-31"),
... famous_for: [ "statue of library", "food" ],
... mayor : {
... name : "Michael Bloomberg",
... party : "I"
... }
... })
WriteResult({ "nInserted" : 1 })
在前一节中,解释了文档是以JSON(严格来说是BSON)格式存在的。所以,我们可以尝试添加一个新的JSON格式的文档。花括号{…}表示由键值对构成的对象(也称为哈希表或映射)。方括号[…]表示一个数组。这些值可以嵌套到任意深度。
通过使用”show collections”命令,可以查看已存在的集合。
> show collections
system.indexes
towns
towns 是之前创建的,但 system.indexes 则始终存在。要将集合的内容列出,可以执行 find() 命令。
> db.towns.find()
{ "_id" : ObjectId("55fabee041ad5a22d4d4d977"), "name" : "New York", "papulation" : 22200000, "last_census" : ISODate("2009-07-31T00:00:00Z"), "famous_for" : [ "statue of library", "food" ], "mayor" : { "name" : "Michael Bloomberg", "party" : "I" } }
与关系型数据库不同的是,Mongo不支持在服务器端进行连接。在一次JavaScript调用中,可以读取文档和嵌套的内容。
新插入的城市的JSON输出中包含ObjectId的_id字段。这与PostgreSQL的数值类型主键的SERIAL自动递增相同。ObjectId由时间戳、客户端机器ID、客户端进程ID和3字节增量计数器组成,总共12字节。
这种自动编号机制的好处在于,所有机器进程都可以在不与其他Mongod实例重复的情况下生成ID。这种设计选择表明了Mongo的分布式性质。
JavaScript –
JavaScript (JavaScript)
Mongo使用JavaScript来支持各种功能,从复杂的mapreduce到简单的帮助工具。
> db.help()
> db.towns.help()
这些指令列出了给定对象的功能。db是JavaScript对象,包含有关当前数据库的信息。db.x是表示名为x的集合的JavaScript对象。这些指令只是JavaScript函数而已。
> typeof db
object
> typeof db.towns
object
> typeof db.towns.insert
function
想要查看函数的源代码,可以不带参数和括号调用函数(可以参考Python,它更接近Ruby)。
> db.towns.insert
function ( obj , options, _allow_dot ){
if ( ! obj )
throw "no object passed to insert!";
var flags = 0;
var wc = undefined;
var allowDottedFields = false;
if ( options === undefined ) {
// do nothing
}
else if ( typeof(options) == 'object' ) {
if (options.ordered === undefined) {
//do nothing, like above
} else {
flags = options.ordered ? 0 : 1;
}
if (options.writeConcern)
wc = options.writeConcern;
if (options.allowdotted)
allowDottedFields = true;
} else {
flags = options;
}
// 1 = continueOnError, which is synonymous with unordered in the write commands/bulk-api
var ordered = ((flags & 1) == 0);
if (!wc)
wc = this.getWriteConcern();
var result = undefined;
var startTime = (typeof(_verboseShell) === 'undefined' ||
!_verboseShell) ? 0 : new Date().getTime();
if ( this.getMongo().writeMode() != "legacy" ) {
// Bit 1 of option flag is continueOnError. Bit 0 (stop on error) is the default.
var bulk = ordered ? this.initializeOrderedBulkOp() : this.initializeUnorderedBulkOp();
var isMultiInsert = Array.isArray(obj);
if (isMultiInsert) {
obj.forEach(function(doc) {
bulk.insert(doc);
});
}
else {
bulk.insert(obj);
}
try {
result = bulk.execute(wc);
if (!isMultiInsert)
result = result.toSingleResult();
}
catch( ex ) {
if ( ex instanceof BulkWriteError ) {
result = isMultiInsert ? ex.toResult() : ex.toSingleResult();
}
else if ( ex instanceof WriteCommandError ) {
result = isMultiInsert ? ex : ex.toSingleResult();
}
else {
// Other exceptions thrown
throw ex;
}
}
}
else {
if ( ! _allow_dot ) {
this._validateForStorage( obj );
}
if ( typeof( obj._id ) == "undefined" && ! Array.isArray( obj ) ){
var tmp = obj; // don't want to modify input
obj = {_id: new ObjectId()};
for (var key in tmp){
obj[key] = tmp[key];
}
}
this.getMongo().insert( this._fullName , obj, flags );
// enforce write concern, if required
if (wc)
result = this.runCommand("getLastError", wc instanceof WriteConcern ? wc.toJSON() : wc);
}
this._lastID = obj._id;
this._printExtraInfo("Inserted", startTime);
return result;
}
那么,我现在要离开shell,创建一个用于将文档添加到towns集合的JavaScript函数。文件夹结构是/mongo/js/js文件夹下。
function insertCity(
name, population, last_census,
famous_for, mayor_info
) {
db.towns.insert({
name : name,
population: population,
last_census : ISODate( last_census ),
famous_for : famous_for,
mayor : mayor_info
});
}
如果把这段代码粘贴到shell中,就可以调用它了。本书只有这一段解释,但是要注意的是,必须加载insert_city.js才能在book中使用该函数。实际操作如下。
$ mongo book ~/Documents/mongodb/js/insert_city.js --shell
MongoDB shell version: 2.6.7
connecting to: book
type "help" for help
Server has startup warnings:
2015-09-17T22:20:25.531+0900 [initandlisten]
2015-09-17T22:20:25.531+0900 [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
>
这样一来,insert_city.js和book被连接在一起了。通过添加–shell选项,可以直接使用函数。
> insertCity("Punxsutawney", 6200, '2008-01-31', ["phil the groundhog"], { name : "Jim Wehrle" } )
> insertCity("Portland", 582000, '2007-09-20', ["beer", "food"], { name : "Sam Adams", party : "D" } )
> db.towns.find()
{ "_id" : ObjectId("55fabee041ad5a22d4d4d977"), "name" : "New York", "papulation" : 22200000, "last_census" : ISODate("2009-07-31T00:00:00Z"), "famous_for" : [ "statue of library", "food" ], "mayor" : { "name" : "Michael Bloomberg", "party" : "I" } }
{ "_id" : ObjectId("55fac70993b946919b1619ea"), "name" : "Punxsutawney", "population" : 6200, "last_census" : ISODate("2008-01-31T00:00:00Z"), "famous_for" : [ "phil the groundhog" ], "mayor" : { "name" : "Jim Wehrle" } }
{ "_id" : ObjectId("55fac75793b946919b1619eb"), "name" : "Portland", "population" : 582000, "last_census" : ISODate("2007-09-20T00:00:00Z"), "famous_for" : [ "beer", "food" ], "mayor" : { "name" : "Sam Adams", "party" : "D" } }
现在,这个集合中已经有三个towns。你可以通过之前提到的db.towns.find()来验证这个结果。
在Mongo中更加有趣。
通过调用find()函数且不带参数,可以获取所有文档。要访问特定的文档,需要设置_id属性。由于_id是ObjectId类型,因此需要通过ObjectId(str)将用于查询的字符串转换为ObjectId类型。
> db.towns.find({ "_id" : ObjectId("55fac70993b946919b1619ea") })
{ "_id" : ObjectId("55fac70993b946919b1619ea"), "name" : "Punxsutawney", "population" : 6200, "last_census" : ISODate("2008-01-31T00:00:00Z"), "famous_for" : [ "phil the groundhog" ], "mayor" : { "name" : "Jim Wehrle" } }
find()函数有一个可选的第二个参数,用于筛选要读取的字段。例如,如果需要城市的名称,则可以传递将name设为1(或true),同时与_id一起使用的参数。
> db.towns.find({ "_id" : ObjectId("55fac70993b946919b1619ea") }, { name : 1 })
{ "_id" : ObjectId("55fac70993b946919b1619ea"), "name" : "Punxsutawney" }
相反地,當讀取除了name以外的所有字段時,將name設置為0(或false或null)。
> db.towns.find({ "_id" : ObjectId("55fac70993b946919b1619ea") }, { name : 0 })
{ "_id" : ObjectId("55fac70993b946919b1619ea"), "population" : 6200, "last_census" : ISODate("2008-01-31T00:00:00Z"), "famous_for" : [ "phil the groundhog" ], "mayor" : { "name" : "Jim Wehrle" } }
似乎可以通过组合字段值、范围和条件来创建任意查询(这与PostgreSQL相同)。例如,要查找首字母以“P”开头且人口少于10,000的城镇,可以使用Perl兼容的正则表达式(PCRE)和范围运算符。
> db.towns.find(
... { name : /^P/, population : { $lt : 10000 } },
... { name : 1, population : 1 }
... )
{ "_id" : ObjectId("55fac70993b946919b1619ea"), "name" : "Punxsutawney", "population" : 6200 }
Mongo的条件运算符遵循field: {$op: value}的格式。$op似乎是代表$ne(不等)等运算符。我可能想使用更简洁的语法,例如field < value,但这是JavaScript代码的特征。
使用JavaScript作为查询语言的优点是可以自己创建运算符。我尝试创建一个条件运算符,以表示“人口大于1万人且小于100万人”。
> var population_range = {}
> population_range['$lt'] = 1000000
1000000
> population_range['$gt'] = 10000
10000
> db.towns.find( { name : /^P/, population : population_range }, { name : 1 } )
{ "_id" : ObjectId("55fac75793b946919b1619eb"), "name" : "Portland" }
而且,不仅可以读取数字范围,也可以读取日期范围。例如,可以搜索在2008年1月31日之前的所有name,其中”last_census”是一个例子。
> db.towns.find(
... { last_census : { $lte : ISODate('2008-01-31') } },
... { _id : 0, name : 1 }
... )
{ "name" : "Punxsutawney" }
{ "name" : "Portland" }
在这个例子中,还要注意到将_id字段设置为0并且没有显示。
最后一句话
因为启动方法等安装方法部分容易被忘记,所以相当重要。
因为发现了一本叫做MongoDB的薄书的PDF,所以我想试着读一读。
由于MongoDB一天的内容很长,所以我分成了两半。明天继续读。
请阅读以上内容。
-
- MacにMongoDBを入れ直した。brew使わずにインストールしてみたメモ – Qiita
-
- 忘れがちな記憶へ mongoの起動方法
- mongoインタラクティブシェルの使い方メモ – ペイパー・プログラマーズ・ダイアリー