MongoDB+pymongo简介(持续更新中)

MongoDB 是一种文件导向的 NoSQL 数据库,由 C++ 撰写而成。

MongoDB简介

  • 什么是NoSQL? 看看这篇zhihu问题足矣. 我的粗浅理解就是NoSQL和SQL不同的地方是, SQL需要定义好一行的标签, 每行数据的维度都是固定的, 格式也是固定的,非常严格. 而NoSQL每个单元的数据都是长短可变的, 甚至每个key对应的value也都是可变的
  • 为什么用MongoDB? 对我而言只是因为他的document文档格式和python的dict以及json格式无缝衔接, 数据单元可伸缩性好, 入门门槛极低. 非常适合小规模的web开发.

mongo shell 简介

官方文档

  • mongo shell是个数据库控制台, 拿mysql简单理解就是登录mysql之后出现的控制台, 可以进行各种数据库操作, mongodb也一样有他自己的控制台, 即mongo shell.

  • 安装见官方文档, 这里不多说了(有时间再补充吧)

0. mongodbd的数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
database -> collection1 -> document1
                        -> document2
                        -> document3
                        -> ...
         
         -> collection2 -> document1
                        -> document2
                        -> document3
                        -> ...
         -> ...
  • mongodb中的最小存储单元是文档(document), 文档组成集合(collection), 集合再组成数据库(database), 其中数据库和集合是可以命名的.
  • document的内容类似于JSON, 或者pyhton中的字典(dict), 由field: value的键值对组成.例子如下. 详细信息请参考官方文档关于document1的说明
1
2
3
4
5
{ 
    "_id": "oranges", 
    "qty": { "in stock": 8, "ordered": 12 },
    "tag": "food"
}

1. 启动/关闭mongod和mongo(OSX系统下,linux类似)

1
2
3
4
5
$ mongod           # 方法1: 启动mongod, 切换至另一个终端开启控制台(因为这个终端会用作打log信息)
$ mongod --fork --logpath /var/log/mongod.log # 方法2: 静默启动, 把log信息存入/var/log/mongod.log下
$ mongo <database> # 连接数据库database, 进入mongo shell
$ mongo --eval "db.getSiblingDB('admin').shutdownServer()" # 关闭数据库
$ sudo service mongod start # linux下的启动指令
  • mongod指令启动数据库, mongo指令登录数据库控制台. 这么说就好理解了, 所以要开两个终端, 要不就用第二种静默启动, 把log保存在文件里的方法继续在同一个终端下工作.

2. mongo shell下的基本操作

1
2
3
4
5
6
7
8
9
> db               # 显示现在使用的数据库
> show dbs         # 查看数据库
> use <database>   # 切换数据库
> show collections # 查看collections
> db.myCollection.insert( { x: 1 } ); # 新建一个名为myCollection的collection, 插入一个document. 注意末尾的分号

> db["3test"].find() # 使用find列出名为3test的collection的文档
> db.getCollection("3test").find().pretty() # 使用pretty优化输出
> quit()             # 退出, 也可以按CTRL+C

3. MongoDB 的CRUD操作

CURD: Create, Read, Update, Delete

  1. 创建/插入文档
  • db.collection.insertOne( { x: 1 } ): 创建collection
  • db.collection.insertMany( [{ x: 1 }, { x: 2 }] ): 插入两个文档, 表示方式类似于python中的数组
  • db.collection.insert(): 以上两种方法都集成到了insert()
  1. 查询文档
  • db.collection.find( { field1: <value>, field2: <value> ... } ): 查询符合键/值对的文档
  • db.collection.find().limit(5): 利用cursor modifier限制搜索范围为5个结果
  1. 更新文档
  • db.collection.update(<filter>, <update/replacment>, <options>)
  • mongodb提供了更新操作符(update operator)来更新文档, 格式如下:
1
2
3
4
5
{
   <operator1>: { <field1>: <value1>, ... },
   <operator2>: { <field2>: <value2>, ... },
   ...
}
  • 操作符介绍
name function
$inc 增加值
$mul 乘以值
$rename 重命名
$setOnInsert 如果更新导致插入一个文档, 则set某个值. 如果更新只是修改某个已经存在的文档则不起作用
$set set文档的某个值
$unset 移除文档的某个field
$min 如果指定的值比已存在的值小, 就更新该值
$max 如果指定的值比已存在的值大, 就更新该值
$currentDate 将某个field的值设定为日期
1
2
3
4
5
6
7
db.inventory.update(
   { item: "paper" },
   {
     $set: { "size.uom": "cm", status: "P" },
     $currentDate: { lastModified: true }
   }
)
  • 其中第一个dict是过滤器<filter>, 即需要更新的文档是满足item键的值为paper的第一个文档
  • 第二个dict为<update/replacment>, 使用$set操作符更新key为"size.uom"的值为"cm", 更新key为status的值为"p", 使用$currentDate更新key为lastModified的值为当前时间, 如果没有这个key就创建一个key
  1. 删除文档
1
2
3
4
db.collection.remove(
   <query>,
   <justOne>
)
  • 第一个参数就是用来搜索符合条件的文档的, 例如{x: 1}会删除所有符合{x: 1}的文档, 第二个参数可选, 是设定只删除第一个true还是删除所有符合条件的文档的false
  • 删除collection使用db.<collectionName>.drop()指令
  • 删除数据库使用db.dropDatabase();(前提是在该数据库下, 即先使用use <dbName>;)
  • 或者直接在终端使用mongo <dbname> --eval "db.dropDatabase()"

pymongo简介

pymongo是使用python操作mongodb的包, 官方文档

1. 建立数据库和collection

1
2
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
  • 连接数据库, 省略参数之后会使用默认host和port, 如例子所示
1
2
db = client.test_database
db = client['test-database']
  • 以上两句是等价的, 创建了一个名为test_database的数据库
1
2
collection = db.test_collection
collection = db['test-collection']
  • 以上两句是等价的, 在test_database中创建了一个叫test_collection的collection.
  • collection是数据库中的一组文档. 文档(document)是mongodb中数据的最小单位, 类似于python中的dict, 或者JSON格式

2. 插入一个document

1
2
3
4
5
6
7
8
import datetime
post = {"author": "Mike",
        "text": "My first blog post!",
        "tags": ["mongodb", "python", "pymongo"],
        "date": datetime.datetime.utcnow()}

posts = db.posts
post_id = posts.insert_one(post).inserted_id
  • 以上语句封装了一个JSON格式的post, 并使用insert_one()方法插入了名为posts的collection.
  • 其中post中的datetime.datetime.utcnow()会被自动转换成BSON格式
  • 插入成功之后会返回一个inserted_id, 对于每个document是独一无二的.
  • 对于每个插入的document, mongodb会自动为其添加一个名为_id的key, 其value也为inserted_id

3. 使用find_one()查询文档

1
2
3
4
5
6
>>> posts.find_one()
>>> {u'_id': ObjectId('...'),
... u'author': u'Mike',
... u'date': datetime.datetime(...),
... u'tags': [u'mongodb', u'python', u'pymongo'],
... u'text': u'My first blog post!'}
  • 直接调用find_one()会返回posts的第一个文档
1
2
posts.find_one({"author": "Mike"})
posts.find_one({"_id": post_id})
  • 定制搜索条件. 其中如果按照_id搜索的话, 必须传入ObjectId类而不是str类, 比如例子中post_id为第2节中返回的ObjectId()
  • 另外, 如果想要自己初始化一个_id可以这么做:
1
2
from bson.objectid import ObjectId
document = client.db.collection.find_one({'_id': ObjectId(post_id)})

4. 使用find()查询多个文档

1
2
for post in posts.find():
   pprint.pprint(post)
  • find()返回一个Cursor实例, 它是一个可遍历实例, 可以像list一样进行遍历
  • 参数使用和find_one()类似, 可以定制搜索条件

5. 编码问题

MongoDB stores data in BSON format. BSON strings are UTF-8 encoded so PyMongo must ensure that any strings it stores contain only valid UTF-8 data. Regular strings (<type ‘str’>) are validated and stored unaltered. Unicode strings (<type ‘unicode’>) are encoded UTF-8 first. The reason our example string is represented in the Python shell as u’Mike’ instead of ‘Mike’ is that PyMongo decodes each BSON string to a Python unicode string, not a regular str.

简单说就是mongodb会把Unicode自动转换成UTF-8存储, 对于已经encode的str则不作处理

6. 使用count()计数

1
2
3
4
5
>>> posts.count()
3

>>> posts.find({"author": "Mike"}).count()
2

7. 高级搜索

1
2
3
d = datetime.datetime(2009, 11, 12, 12)
for post in posts.find({"date": {"$lt": d}}).sort("author"):
    pprint.pprint(post)
  • 此处使用$lt搜索出了所有在d之前的所有posts, 并按照"author"关键词升序排列

8. 创建自己的编号(index)

1
2
3
4
>>> result = db.profiles.create_index([('user_id', pymongo.ASCENDING)],
...                                   unique=True)
>>> sorted(list(db.profiles.index_information()))
[u'_id_', u'user_id_1']
  • index_information()查询一下, 发现现在我们有了两个index, 一个是插入文档时自创的_id, 还有一个是刚刚创建的user_id
1
2
3
4
>>> user_profiles = [
...     {'user_id': 211, 'name': 'Luke'},
...     {'user_id': 212, 'name': 'Ziltoid'}]
>>> result = db.profiles.insert_many(user_profiles)
  • 使用insert_many()方法插入两个文档之后,再试试插入拥有相同user_id的文档试试
1
2
3
4
5
6
>>> new_profile = {'user_id': 213, 'name': 'Drew'} 
>>> duplicate_profile = {'user_id': 212, 'name': 'Tommy'}
>>> result = db.profiles.insert_one(new_profile)  # 插入编号213, ok
>>> result = db.profiles.insert_one(duplicate_profile)  # 插入编号212, 已经存在, 抛出DuplicateKeyError
Traceback (most recent call last):
DuplicateKeyError: E11000 duplicate key error index: test_database.profiles.$user_id_1 dup key: { : 212 }

Refer: https://docs.mongodb.com/manual/mongo/ https://docs.mongodb.com/manual/crud/ https://api.mongodb.com/python/current/tutorial.html