2.5 操作数据库
现在就可以使用自带的MongoDB Shell工具来操作数据库了。 当然,也可以使用各种编程语言的驱动来使用MongoDB, 但自带的MongoDB Shell工具可以方便地管理数据库。
2.5.1 连接数据库
打开一个Session输入“/Apps/mongo/bin/mongo”,如果出现下面的提示,说明已经连接上数据库,可以执行操作了:
[root@localhost ~]# /Apps/mongo/bin/mongo
MongoDB shell version: 1.8.1
connecting to: test
>
默认 Shell 连接的是本机localhost 上的 test库,“connecting to:”会显示正在使用的数据库名称,想换数据库的话可以用“use mydb”来实现。
2.5.2 插入记录
建立一个things的集合并写入一些数据,建立两个对象j和t , 并保存到集合中,如下面的代码所示(其中“>”为 Shell 输入提示符):
> j = { name : "mongo" };
{"name" : "mongo"}
> t = { x : 3 };
{ "x" : 3 }
//插入数据
> db.things.save(j);
> db.things.save(t);
> db.things.find();
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
>
以下几点需要注意:
q 不需要预先创建一个集合,在第一次插入数据时会自动创建。
q 在文档中可以存储任何结构的数据, 但在实际应用中存储的还是相同类型文档的集合。此特性很灵活, 不需要类似alter table 语句来修改数据结构。
q 每次插入数据时,集合中都会有一个ID(_id)。
下面插入一些数据,如下面的代码所示:
//用for循环来插入数据
> for( var i = 1; i < 10; i++ ) db.things.save( { x:4, j:i } );
> db.things.find();
{"name" : "mongo" , "_id" : ObjectId("497cf60751712cf7758fbdbb")}
{"x" : 3 , "_id" : ObjectId("497cf61651712cf7758fbdbc")}
{"x" : 4 , "j" : 1 , "_id" : ObjectId("497cf87151712cf7758fbdbd")}
{"x" : 4 , "j" : 2 , "_id" : ObjectId("497cf87151712cf7758fbdbe")}
{"x" : 4 , "j" : 3 , "_id" : ObjectId("497cf87151712cf7758fbdbf")}
{"x" : 4 , "j" : 4 , "_id" : ObjectId("497cf87151712cf7758fbdc0")}
{"x" : 4 , "j" : 5 , "_id" : ObjectId("497cf87151712cf7758fbdc1")}
{"x" : 4 , "j" : 6 , "_id" : ObjectId("497cf87151712cf7758fbdc2")}
{"x" : 4 , "j" : 7 , "_id" : ObjectId("497cf87151712cf7758fbdc3")}
{"x" : 4 , "j" : 8 , "_id" : ObjectId("497cf87151712cf7758fbdc4")}
请注意, 这里循环次数是10, 但是只显示到第8条, 还有2条数据没有显示。如果想继续查询下面的数据只需要使用“it”命令,如下面的代码所示:
{ "_id" : ObjectId("4c220a42f3924d31102bd866"), "x" : 4, "j" : 17 }
{ "_id" : ObjectId("4c220a42f3924d31102bd867"), "x" : 4, "j" : 18 }
has more
//此处用it执行继续查询
> it
{ "_id" : ObjectId("4c220a42f3924d31102bd868"), "x" : 4, "j" : 19 }
{ "_id" : ObjectId("4c220a42f3924d31102bd869"), "x" : 4, "j" : 20 }
从技术上讲, find() 返回一个游标对象,但在上面的例子里, 并没有拿到一个游标的变量,所以 Shell 自动遍历游标,返回一个初始化的set,并允许继续用 it 迭代输出。当然,也可以直接用游标来输出,不过这是“游标”部分的内容了。
扩展阅读 什么是_id key
MongoDB支持的数据类型中,_id是其自有产物。存储在MongoDB集合中的每个文档(document)都有一个默认的主键_id,这个主键名称是固定的,它可以是MongoDB支持的任何数据类型,默认是ObjectId。在关系数据库Schema设计中,主键大多数是数值型的,比如常用的int和long,并且通常主键的取值由数据库自增获得,这种主键数值的有序性有时也表明了某种逻辑。而MongoDB在设计之初就定位于分布式存储系统,所以它不支持自增主键。
当向一个集合中写入一条文档时,系统会自动生成一个名为_id 的key,如下面的代码所示:
> db.c1.find()
{ "_id" : ObjectId("4fb5faaf6d0f9d8ea3fc91a8"), "name" : "Tony", "age" : 20 }
{ "_id" : ObjectId("4fb5fab96d0f9d8ea3fc91a9"), "name" : "Joe", "age" : 10 }
这里多出一个类型为ObjectId的key值,在插入时并没有指定,类似Oracle的rowid信息,这属于自动生成的。
在MongoDB中,每一个集合都必须有一个_id字段,字段类型默认是ObjectId。注意,可以不是ObjectId,例如下面的_id=3的条目:
> db.c1.find()
{ "_id" : ObjectId("4fb5faaf6d0f9d8ea3fc91a8"), "name" : "Tony", "age" : 20 }
{ "_id" : ObjectId("4fb5fab96d0f9d8ea3fc91a9"), "name" : "Joe", "age" : 10 }
{ "_id" : 3, "name" : "Bill", "age" : 55 }
虽然_id的类型可以自由指定,但是在同一个集合中必须唯一,如果插入重复的值,系统将会抛出异常,如下面的代码所示:
> db.c1.insert({_id:3, name:"Bill_new", age:55})
E11000 duplicate key error index: test.c1.$_id_ dup key: { : 3.0 }
>
因为前面已经插入了一条_id=3的记录,所以再插入相同的文档就不允许了。
2.5.3 查询记录
在没有深入讲解查询之前, 先看看怎样从查询中返回一个游标对象。可以简单地通过 find() 来查询,返回一个任意结构的集合,而如何实现特定的查询稍后讲解。
- 普通查询
一般的查询可以通过 while 循环输出,如下面的代码所示:
> var cursor = db.things.find();
> while (cursor.hasNext()) printjson(cursor.next());
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4c220a42f3924d31102bd857"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85a"), "x" : 4, "j" : 5 }
上面的例子显示了游标风格的迭代输出,hasNext() 函数用于判断是否还有数据,如果有则调用 next() 函数将数据取出来。
当使用的是 JavaScript Shell时,可以用JavaScript的forEach特性,这样就可以输出游标了。下面的例子就是使用 forEach() 循环输出数据,但forEach() 必须定义一个函数供每个游标元素调用,如下面的代码所示:
> db.things.find().forEach(printjson);
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4c220a42f3924d31102bd857"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85a"), "x" : 4, "j" : 5 }
在 MongoDB Shell 里,也可以把游标当成数组来用,如下面的代码所示:
> var cursor = db.things.find();
> printjson(cursor[4]);
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "x" : 4, "j" : 3 }
使用游标时请注意占用内存的问题,特别是很大的游标对象,有可能会内存溢出,所以应该用迭代的方式来输出。下面的示例是把游标转换成真实的数组类型:
> var arr = db.things.find().toArray();
> arr[5];
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "x" : 4, "j" : 4 }
注意 这些特性只是在MongoDB Shell里使用,不是所有的其他应用程序驱动都支持。如果有其他用户在集合里第一次或者最后一次调用next(), 可能得不到游标里的数据,所以要明确地锁定要查询的游标。
- 条件查询
下面的示例说明了如何执行一个类似SQL的查询,并演示了如何在 MongoDB 里实现,这是在MongoDB Shell里查询,当然,也可以用其他的应用程序驱动或者语言来实现。
查询条件是 { a:A, b:B, ... } ,类似“where a==A and b==B and ...”。例如,SQL查询:
SELECT * FROM things WHERE x=4
用MongoDB 实现:
> db.things.find({x:4}).forEach(printjson);
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }
{ "_id" : ObjectId("4c220a42f3924d31102bd857"), "x" : 4, "j" : 2 }
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "x" : 4, "j" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "x" : 4, "j" : 4 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85a"), "x" : 4, "j" : 5 }
上面显示的是所有元素,也可以返回特定的元素,类似于返回表里某字段的值,只需要在“find({x:4})”里指定元素的名称,如下所示:
> db.things.find({x:4}, {j:true}).forEach(printjson);
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "j" : 1 }
{ "_id" : ObjectId("4c220a42f3924d31102bd857"), "j" : 2 }
{ "_id" : ObjectId("4c220a42f3924d31102bd858"), "j" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd859"), "j" : 4 }
{ "_id" : ObjectId("4c220a42f3924d31102bd85a"), "j" : 5 }
上面查询命令中的“{j:true}”显式地指明结果集只返回列“j”的值,而其他列如“x”的值并没有返回。
- findOne()语法
MongoDB Shell为了避免游标可能带来的开销,提供了一个findOne() 函数。这个函数和 find() 函数一样,不过它返回的是游标里的第一条数据,或者返回null,即空数据。
例如name=“mongo” 可以用很多方法来实现,如用 next() 来循环游标或者当成数组返回第一个元素,而用 findOne() 方法更简单和高效,如下面的代码所示:
> printjson(db.things.findOne({name:"mongo"}));
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
- 通过limit限制结果集数量
如果需要限制结果集的长度,可以调用limit方法,如下面的代码所示:
> db.things.find().limit(3);
{ "_id" : ObjectId("4c2209f9f3924d31102bd84a"), "name" : "mongo" }
{ "_id" : ObjectId("4c2209fef3924d31102bd84b"), "x" : 3 }
{ "_id" : ObjectId("4c220a42f3924d31102bd856"), "x" : 4, "j" : 1 }
强烈推荐这种解决性能问题的方法,通过限制条数来减少网络传输,同时,limit方法也广泛应用于分页技术中。
2.5.4 修改记录
如果需要修改表中的记录,可以调用update方法。
例如,将列name的值从“mongo”修改为“mongo_new”,如下面的代码所示:
db.things.update({name:"mongo"},{$set:{name:"mongo_new"}});
接下来通过执行“db.things.find()”命令查询列name的值是否改过来了,如下面的代码所示:
> db.things.find();
{ "_id" : ObjectId("4faa9e7dedd27e6d86d86371"), "x" : 3 }
{ "_id" : ObjectId("4faa9e7bedd27e6d86d86370"), "name" : "mongo_new" }
经验证,name的值已经改为“mongo_new”了。
2.5.5 删除记录
如果要删除表中的记录,可以调用remove方法。
例如,将用户name是“mongo_new”的记录从集合things中删除,如下面的代码所示:
> db.things.remove({name:"mongo_new"});
> db.things.find();
{ "_id" : ObjectId("4faa9e7dedd27e6d86d86371"), "x" : 3 }
经验证,该记录确实被删除了。