本文将介绍如何使用pymongo和mongoengine在Python中操作MongoDB,同时简单比较NoSQL与SQL
一、MongoDB简介
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 是一个介于关系数据库(SQL)和非关系数据库(NoSQL)之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
MongoDB 是常见的文档数据库(也称为文档型数据库)是旨在将半结构化数据存储为文档的一种数据库。文档数据库通常以 JSON 或 XML 格式存储数据。由于文档数据库的no-schema特性,可以存储和读取任意数据。由于使用的数据格式是JSON或者BSON,因为JSON数据是自描述的,无需在使用前定义字段,读取一个JSON中不存在的字段也不会导致SQL那样的语法错误,可以解决关系型数据库表结构schema扩展不方便的问题
二、SQL 与 NoSQL
MongoDB 是常见的NoSQL数据库之一。
SQL (Structured Query Language) 数据库,指关系型数据库。主要代表:SQL Server,Oracle,MySQL,PostgreSQL。特点如下:
- 模型是关系型的;
- 数据被存放在表中;
- 适用于每条记录都是相同类型并具有相同属性的情况;
- 存储规范需要预定义结构;
- 添加新的属性意味着你必须改变整体架构;
- ACID事务支持;
NoSQL(Not Only SQL)泛指非关系型数据库。主要代表:MongoDB,Redis,CouchDB。特点如下:
- 模型是非关系型的;
- 可以存储Json、键值对等(决定于NoSQL数据库类型);
- 并不是每条记录都要有相同的结构;
- 添加带有新属性的数据时,不会影响其他;
- 支持ACID事务,根据使用的NoSQL的数据库而有所不同;
- 一致性可以改变;
- 横向扩展;
非关系型数据库(NoSQL)具有如下优缺点:
优点:
- 简单的扩展:典型例子是Cassandra,由于其架构是类似于经典的P2P,所以能通过轻松地添加新的节点来扩展这个集群;
- 快速的读写:主要例子有Redis,由于其逻辑简单,而且纯内存操作,使得其性能非常出色,单节点每秒可以处理超过10万次读写操作;
- 低廉的成本:这是大多数分布式数据库共有的特点,因为主要都是开源软件,没有昂贵的License成本;
缺点:
- 不提供对SQL的支持:如果不支持SQL这样的工业标准,将会对用户产生一定的学习和应用迁移成本;
- 支持的特性不够丰富:现有产品所提供的功能都比较有限,大多数NoSQL数据库都不支持事务,也不像MS SQL Server和Oracle那样能提供各种附加功能,比如BI和报表等;
- 现有产品的不够成熟:大多数产品都还处于初创期,和关系型数据库几十年的完善不可同日而语;
为什么选择NoSQL?
并不是任何场景,NoSQL都要优于关系型数据库。在以下场景,它通常有更优秀的表现:
- 数据库表schema经常变化
- 数据库表字段是复杂数据类型
- 高并发数据库请求
- 海量数据的分布式存储
三、pymongo
1.安装
$ pip install pymongo
2.连接数据库server
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
MongoClient两个参数为数据库地址和端口,端口默认为27017
2.连接数据库和数据集
# 连接数据库
db = client['dbname']
# 连接数据集
col = client['dbname']['colname']
或者
db = client.dbname
col = client.dbname.colname
以上方法也可用于新建数据库和数据集,但需注意,在数据集插入第一条记录前,数据库和数据集不会被真正创建。
3.查看server中已有数据库和数据集
# 查看已有数据库列表
client.list_database_names()
# 查看已有数据集列表
db.list_collection_names()
输出结果:
['Javdb', 'admin', 'config', 'local', 'zhihu']
['question', 'collection', 'beauty']
4.插入数据
# 插入单条数据
data = {'girlname': '兰追', 'upvote': 1048}
col.insert(data)
# 插入多条数据
data_list = []
data1 = {'girlname': '兰追', 'upvote': 1048}
data2 = {'girlname': '柚子小姐', 'upvote': 863}
data_list.append(data1)
data_list.append(data2)
col.insert_many(data_list)
insert 插入单条数据,insert_many 插入多条数据组成的列表
5.更新数据
col.update(data, {'$set': data}, upsert)
上面的方法,通过{‘$set’: data}条件筛选出需要更新的字段,更新数据data到符合条件的记录中,upsert参数控制当没有符合筛选条件的记录时,是否将data作为新的记录插入。当upsert为True时,将插入新的记录。
插入数据时,可以使用update,并设置select为需要插入的数据,upsert=True,采用这种方法可以避免插入重复的数据。更新结果的updatedExisting字段为True时表示记录存在,False表示不存在。
6.查询数据
# 查询所有
col.find()
# 条件查询
col.find({'girlname':'兰追'})
find()方法返回cursor对象,可通过cursor遍历满足条件的记录。find()可接收操作符作为查询的筛选条件,以下列举常用的操作符:
# 常用操作符
# 大于
col.find({'upvote': {'$gt': 20}})
# 小于
col.find({'upvote': {'$lt': 20}})
# 大于等于
col.find({'upvote': {'$gte': 20}})
# 小于等于
col.find({'upvote': {'$lte': 20}})
# 大于,小于,且不等于
col.find({'upvote': {'$gt': 20, '$lt': 30, '$ne': 25}})
# 字段是否存在
col.find({'girlname': {'$exists': True}})
col.find({'girlname': {'$exists': False}})
# 或
col.find({'$or': [{a: 1}, {b: 2}]})
# a 是否在 [1,2,3] 中的任意一个
col.find({a: {'$in': [1,2,3]}})
col.find({a: {'$nin': [1,2,3]}})
# a 全部满足
col.find({a: {'$all': [1,2,3]}})
其他操作符可参考:
7.删除数据
# 全部删除
col.remove()
# 条件删除
col.remove({'girlname':'兰追'})
remove也可接收操作符作为筛选条件。
8.其他方法
# 查看记录总数
col.find().count()
# 排序,默认ASCENDING升序
col.find().sort([('girlname', pymongo.ASCENDING), ('upvote', pymongo.DESCENDING)])
# 删除整个数据集
col.drop()
三、mongoengine
1.安装
$ pip install mongoengine
2.连接数据库
db = connect('zhihu', host='localhost', port=27017)
3.定义数据模型
from mongoengine import *
class Todo(Document):
meta = {
'collection': 'todo',
'ordering': ['-create_at'],
'strict': False,
}
task = StringField()
create_at = DateTimeField(default=datetime.now)
is_completed = BooleanField(default=False)
在上面的代码中,我们定义了一个Todo
类,它继承mongoengine中的Docment类,用以实现类操作,meta
字典设置了collection
,ordering
和strict
,其中ordering
的值可以指定你的QuerySet的默认顺序,strict
的值指定是否使用严格模式,默认值是True
,也就是使用严格模式,这就意味着如果数据库的记录如果存在某些字段没有在我们的数据模型中声明,那程序在运行时会产生一个FieldDoesNotExist
的错误。因此,我们的数据模型定义最好跟记录中的字段保持一致。
4.查询数据
# 查询所有数据
Todo.objects().all()
# 条件查询
task = 'cooking'
todo = Todo.objects(task=task).first()
其中,first()
方法会取出满足条件的第 1 条记录。
mongoengine同样可以使用操作符作为筛选条件,操作符的表示形式为:加在关键字后面使用”__+操作符”(此处是两个” _ “),例如:
# create_at大于12
Todo.objects(create_at__gt=12).all()
常见操作符:
- ne:不等于
- lt:小于
- lte:小于或等于
- gt:大于
- gte:大于或等于
- not:对一个操作符取否,例如create_at__not__gt
- in:值在列表中
- nin:值不在列表中
- mod:值%a==b,a和b用(a,b)的方式传递
- all:列表中的所有值都在该字段中
- size:列表的大小
- existes:在该字段中存在这个值
更多操作符:
MongoEngine所使用的操作符 www.jianshu.com
5.插入数据
# 方法1
todo1 = Todo(task='task 1', is_completed=False)
todo1.save()
# 方法2
task1 = Todo()
task1['task'] = 'task 1'
task1['is_completed'] = False
task1.save()
6.更新数据
task = 'task 1'
todo = Todo.objects(task=task).first() # 先查找
todo.update(is_completed=True) # 再更新
7.删除数据
task = 'task 6'
todo = Todo.objects(task=task).first() # 先查找
todo.delete() # 再删除
8.其他方法
# 高级查询
from mongoengine.queryset.visitor import Q
# 查找性别为男, 且手机号为 18034567890 的记录
result = Student.objects(Q(gender='male') & Q(contact__phone='18034567890'))
# 查找性别为男, 或者手机号为 18034567890 的记录
result = Student.objects(Q(gender='male') | Q(contact__phone='18034567890'))
# 查找性别为男, 且手机号为 18034567890 的记录 或者 性别为女的记录
result = Student.objects((Q(gender='male') & Q(contact__phone='18034567890')) | Q(gender='female'))