教程
这个教程只在介绍如何使用MongoDB和PyMongo
准备前提
在我们开始之前,首先确认你已经正确安装了PyMongo ,在python shell 中如果下面的命令没有报错,则证明安装正确
>>>import pymongo
这个教程假设你已经正确安装了MongoDB,而且已经有一个instance运行在默认的host和port,如果你已经正确安装了mongodb可以使用下面进行启动
$mongod
和MongoClient建立连接
使用pymongo的第一步是创建一个MongoClient 用于和运行的mongod实例进行连接
>>>from pymongo import MongoClient
>>>client = MongoClient()
上面的代码将会连接默认的Host和port.我们也可以手动指定host 和端口,比如:
>>>client = MongoClient(‘localhost‘,27017)
或者使用mongodb url 格式地址:
>>>client = MongoClient(‘mongodb://localhost:27017/‘)
获取数据库(Database)
一个mongodb实例可以支持多个独立的databse. 当使用pymongo的时候,你可以使用类似属性风格的方式访问MongoClient实例的database:
>>>db = client.test_database
或者使用字典风格也可以
>>>db = client[‘test-database‘]
获取一个集合(collection)
一个集合(collection)是一系列储存在mongodb上的文档(document)的组,有点类似关系型数据库的表table。获取集合的方法类似获取数据库
>>>collection = db.test_collection
或者使用字典:
>>>collection = db[‘test-collection‘]
需要注意的是mongodb中的databases或者documents都是属于延迟创建(lazily created)的,上面所有的commands其实都没有对数据库进行任何实质性的操作。上面代码中的databases或者documents直到第一个documents被添加insert时才被创建
文档(documents)
数据在mongodb里面以JSON风格文档来存储和体现。在pymongo中我们使用字典来体现documents。在下面的例子中字典被用来表现一篇博客:
>>>import datetime:
>>>post = {"author":"Mike",
"text":"My first blog post",
"tags"["mongodb",‘python‘,‘pymongo‘],
"date": datetime.datetime.utcnow()}
注意 documents可以包含python自己的类型(比如datetime.datetime实例),这些数据会被自动转化为bson类型
添加文档(insert a document)
可以使用insert_one()来添加一条documents:
>>>posts = db.posts
>>>post_id = posts.insertone(post).inserted_id
>>>post_id
ObjectId(‘...‘)
当添加documents时候,如果‘id’值没有指定,则系统会自动生成一个‘id‘值,这个‘_id‘值在整个collection中是唯一的。inssert_one()返回一个InsertOneResult结果实例。
在添加了第一个document时,posts这个集合才在服务器上真正的创建。我们可以列出database中的集合collection来确认:
>>>db.collection_names(include_system_collections=False)
[u‘posts‘]
使用find_one()来得到一条document
最常使用的查询就是find_one()。他返回一条匹配的文档(如果没有匹配会返回None)。他经常被用在只有一条文档匹配,或者指向得到第一个匹配文档的情况。下面我们用他来返回post集合的第一条文档:
>>>posts.finde_one()
{u‘date‘:datetime.datetime(...),u‘text‘:u‘My first blog post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘mongodb‘, u‘python‘, u‘pymongo‘]}
返回的结果就是我们刚才添加的那条文档
注意:返回的文档中包含"_id",这是系统自动生成的
可以给find_one()添加一些匹配的参数:
>>>posts.find_one({‘author‘:"mike"})
{u‘date‘: datetime.datetime(...), u‘text‘: u‘My first blog post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘mongodb‘, u‘python‘, u‘pymongo‘]}
如果我们尝试一个不同的author,比如‘Eliot’就得不到结果:
>>> posts.find_one({"author": "Eliot"})
>>>
通过ObjectId查询
我们也可以通过ObjectId进行查询:
>>> post_id
ObjectId(...)
>>> posts.find_one({"_id": post_id})
{u‘date‘: datetime.datetime(...), u‘text‘: u‘My first blog post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘mongodb‘, u‘python‘, u‘pymongo‘]}
注意ObjectId并不是一个简单的string
>>> post_id_as_str = str(post_id)
>>> posts.find_one({"_id": post_id_as_str}) # No result
>>>
当我们进行web开发的时候,经常会需要将url中得到的ObjectId转换成pymongo可以识别的ObjectId,然后将他传递给find_one:
from bson.objectid import OjbectId
# The web framework gets post_id from the URL and passes it as a string
def get(post_id):
#Convert from string to ObjectId:
document = client.db.collection.find_one({‘_id‘: ObjectId(post_id)})
关于Unicode
你可能已经发现了我们刚才保存的常规的Python string和我们从服务器得到的并不一样(比如u‘Mike‘ ‘Mike‘),这里我们简单的解释一下:
mongodb保存使用BSON 格式来保存数据。BSON string 都是UTF-8编码,所以pymongo必须确保所有的sting必须都是合法的UTF-8数据。常规的string储存不变???(regular string are validated and stored unaltered), Unicode编码的string首先会已utf-8编码。我们例子里面显示为u‘Mike‘而不是‘Mike‘的原因就是,pymongo首先解码每个BSON为PYHON unicode string,而不是常规的str (妈的,不懂)
批量添加 Bulk inserts
可以使用insert_many() 一次性添加多个document
>>> new_posts = [{"author": "Mike",
... "text": "Another post!",
... "tags": ["bulk", "insert"],
... "date": datetime.datetime(2009, 11, 12, 11, 14)},
... {"author": "Eliot",
... "title": "MongoDB is fun",
... "text": "and pretty easy too!",
... "date": datetime.datetime(2009, 11, 10, 10, 45)}]
>>> result = posts.insert_many(new_posts)
>>> result.inserted_ids
[ObjectId(‘...‘), ObjectId(‘...‘)]
关于上面这个例子需要注意两点:
- 上面例子中insert_many()反悔了两个ObjectId实例,分别对应两个document
- new_posts[1] 和其他的post的数据结构“shape”是不同的 - 一个有tag属性,一个有title属性,这就是我们常提到mongodb的自由结构(schema-free)特性 区别于传统的关系型数据库
查询得到多个文档
我们可以使用find()来一次性返回多个文档。find()返回一个指针cursor实例,可以用来遍历所有匹配的documents。
例如我可以用它来遍历所有的posts集合的所有documents:
>>>for post in posts.find():
... post
...
{u‘date‘: datetime.datetime(...), u‘text‘: u‘My first blog post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘mongodb‘, u‘python‘, u‘pymongo‘]}
{u‘date‘: datetime.datetime(2009, 11, 12, 11, 14), u‘text‘: u‘Another post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘bulk‘, u‘insert‘]}
{u‘date‘: datetime.datetime(2009, 11, 10, 10, 45), u‘text‘: u‘and pretty easy too!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Eliot‘, u‘title‘: u‘MongoDB is fun‘}
就像find_one()一样,我们也可以给他传递参数
>>> for post in posts.find({"author": "Mike"}):
... post
...
{u‘date‘: datetime.datetime(...), u‘text‘: u‘My first blog post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘mongodb‘, u‘python‘, u‘pymongo‘]}
{u‘date‘: datetime.datetime(2009, 11, 12, 11, 14), u‘text‘: u‘Another post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘bulk‘, u‘insert‘]}
计数
可以使用count()方法进行计数,而不是返回一个完整的查询
>>> posts.count()
3
或者对返回的查询进行计数
>>> posts.find({"author": "Mike"}).count()
2
范围查询
mongodb支持多种不同类型的高级查询。比如我可以查询一个早于特定时间的posts,然后按照author排序返回
>>> d = datetime.datetime(2009, 11, 12, 12)
>>> for post in posts.find({"date": {"$lt": d}}).sort("author"):
... print post
...
{u‘date‘: datetime.datetime(2009, 11, 10, 10, 45), u‘text‘: u‘and pretty easy too!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Eliot‘, u‘title‘: u‘MongoDB is fun‘}
{u‘date‘: datetime.datetime(2009, 11, 12, 11, 14), u‘text‘: u‘Another post!‘, u‘_id‘: ObjectId(‘...‘), u‘author‘: u‘Mike‘, u‘tags‘: [u‘bulk‘, u‘insert‘]}
这里我们使用“$lt”操作符来做一个范围查询,然后使用sort()来对结果进行排序
索引
如果想让上述查询更快,我们可以给“date” 和"author"属性增加 元素索引。
为了显示增加索引后的效率提升,我们使用explain()方法来得到查询动作的次数:
>>> posts.find({"date": {"$lt": d}}).sort("author").explain()["cursor"]
u‘BasicCursor‘
>>> posts.find({"date": {"$lt": d}}).sort("author").explain()["nscanned"]
3
可以看到在没有索引的情况下,使用 基本指针basicCursor 总共扫描了集合中的3个文档,现在我们添加索引
>>>from pymongo import ASCENDING, DESCENDING
>>>posts.create_index([("date", DESCENDING), ("author"), ASCENDING])
u‘date_-1_author_1‘
>>>posts.find({‘date‘:{"$lt":d}}).sort("author").explain([‘curosr‘])
u‘BtreeCursor date_-1_author_1
>>> posts.find({"date": {"$lt": d}}).sort("author").explain()["nscanned"]
2
可以看到这次的衩裙使用 Btree指针 BtreeCursor 只扫描了两个文档就得到了结果