NOSQL数据库之MongoDB


一、NoSQL概述

       如今,大多数的计算机系统(包括服务器、PC、移动设备等)都会产生庞大的数据量。其实,早在2012年的时候,全世界每天产生的数据量就达到了2.5EB(艾字节,?)。这些数据有很大一部分是由关系型数据库来存储和管理的。 早在1970年,E.F.Codd发表了论述关系型数据库的著名论文“A relational model of data for large shared data banks”,这篇文章奠定了关系型数据库的基础并在接下来的数十年时间内产生了深远的影响。实践证明,关系型数据库是实现数据持久化最为重要的方式,它也是大多数应用在选择持久化方案时的首选技术。

       NoSQL是一项全新的数据库革命性运动,虽然它的历史可以追溯到1998年,但是NoSQL真正深入人心并得到广泛的应用是在进入大数据时候以后,业界普遍认为NoSQL是更适合大数据存储的技术方案,这才使得NoSQL的发展达到了前所未有的高度。2012年《纽约时报》的一篇专栏中写到,大数据时代已经降临,在商业、经济及其他领域中,决策将不再基于经验和直觉而是基于数据和分析而作出。事实上,在天文学、气象学、基因组学、生物学、社会学、互联网搜索引擎、金融、医疗、社交网络、电子商务等诸多领域,由于数据过于密集和庞大,在数据的分析和处理上也遇到了前所未有的限制和阻碍,这一切都使得对大数据处理技术的研究被提升到了新的高度,也使得各种NoSQL的技术方案进入到了公众的视野。NoSQL关注的是对数据高并发的读写和对海量数据的存储等,与关系型数据库相比,在架构和数据模型方面做了减法,而在扩展和并发方面做了“加法”。优缺点如下:

  • 优势:简单的扩展、快速的读写、低廉的成本、灵活的数据模型。
  • 不足:不提供对SQL的支持、支持的特性不够丰富、现有产品的不够成熟。

        NoSQL数据库按照其存储类型可以大致分为以下几类:

类型部分代表特点
列族数据库 HBase
Cassandra
Hypertable
顾名思义是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的I/O优势,适合于批量数据处理和即时查询。
文档数据库 MongoDB
CouchDB
ElasticSearch
文档数据库一般用类JSON格式存储数据,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能,但不提供对参照完整性和分布事务的支持。
KV数据库 DynamoDB
Redis
LevelDB
可以通过key快速查询到其value,有基于内存和基于磁盘两种实现方案。
图数据库 Neo4J
FlockDB
JanusGraph
使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。图数据库从设计上,就可以简单快速的检索难以在关系系统中建模的复杂层次结构。
对象数据库 db4o
Versant
通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。

Redis:https://www.cnblogs.com/zhangyafei/p/10138629.html

二、MongoDB简介

MongoDB是2009年问世的一个面向文档的数据库管理系统,由C++语言编写,旨在为Web应用提供可扩展的高性能数据存储解决方案。虽然在划分类别的时候后,MongoDB被认为是NoSQL的产品,但是它更像一个介于关系数据库和非关系数据库之间的产品,在非关系数据库中它功能最丰富,最像关系数据库。语法有点类似JavaScript面向对象的查询语言,它是一个面向集合的,模式自由的文档型数据库。

MongoDB将数据存储为一个文档,一个文档由一系列的“键值对”组成,其文档类似于JSON对象,但是MongoDB对JSON进行了二进制处理(能够更快的定位key和value),因此其文档的存储格式称为BSON。关于JSON和BSON的差别大家可以看看MongoDB官方网站的文章《JSON and BSON》

目前,MongoDB已经提供了对Windows、MacOS、Linux、Solaris等多个平台的支持,而且也提供了多种开发语言的驱动程序,Python当然是其中之一。

2.1 MongoDB的安装和配置

可以从MongoDB的官方下载链接下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。

官网:https://www.mongodb.com/download-center/community

windows

下载安装包直接安装

centos:

# 官网:https://www.mongodb.com/download-center/community
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.3.tgz
tar -xvf mongodb-linux-x86_64-rhel70-4.2.3.tgz
mv mongodb-linux-x86_64-rhel70-4.2.3 mongodb
mkdir data
touch dblogs
vi /etc/profile
export PATH=$PATH:/usr/local/mongodb/bin
source /etc/profile
# 参数启动
mongod --dbpath=/usr/local/mongodb/data/ --logpath=/usr/local/mongodb/dblogs --fork  # 后台启动
# 配置文件启动
vi mongodb.conf
port=27017 #端口
dbpath=/usr/local/mongodb/data #数据库存文件存放目录 
logpath=/usr/local/mongodb/dblogs  #日志文件存放路径 
fork=true #以守护进程的方式运行,创建服务器进程
bind_ip=0.0.0.0
auth=true # 或者 noauth=true

mongod --config mongodb.conf

# 查询系统进程
ps -ef | grep mongod

关闭mongodb
pkill mongod 或者 killall mongod

windows远程连接

C:\Users\fei>mongo --host 182.254.179.186
MongoDB shell version v3.6.5
connecting to: mongodb://182.254.179.186:27017/
MongoDB server version: 4.2.3
WARNING: shell and server versions do not match
Server has startup warnings:
2020-02-14T13:04:58.978+0800 I  STORAGE  [initandlisten]
2020-02-14T13:04:58.978+0800 I  STORAGE  [initandlisten] ** WARNING: Using the X
FS filesystem is strongly recommended with the WiredTiger storage engine
2020-02-14T13:04:58.978+0800 I  STORAGE  [initandlisten] **          See http://
dochub.mongodb.org/core/prodnotes-filesystem
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] ** WARNING: Access cont
rol is not enabled for the database.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] **          Read and wr
ite access to data and configuration is unrestricted.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] ** WARNING: You are run
ning this process as the root user, which is not recommended.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] ** WARNING: /sys/kernel
/mm/transparent_hugepage/enabled is 'always'.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] **        We suggest se
tting it to 'never'
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] ** WARNING: /sys/kernel
/mm/transparent_hugepage/defrag is 'always'.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] **        We suggest se
tting it to 'never'
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten] ** WARNING: soft rlimit
s too low. rlimits set to 7282 processes, 100001 files. Number of processes shou
ld be at least 50000.5 : 0.5 times number of files.
2020-02-14T13:05:01.297+0800 I  CONTROL  [initandlisten]
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB

三、MongoDB基本概念及增删改查

我们通过与关系型数据库进行对照的方式来说明MongoDB中的一些概念。

SQLMongoDB解释(SQL/MongoDB)
database database 数据库/数据库
table collection 二维表/集合
row document 记录(行)/文档
column field 字段(列)/域
index index 索引/索引
table joins --- 表连接/嵌套文档
primary key primary key 主键/主键(_id字段)
  • 基本命令
  • 操作数据库
//显示当前使用数据库
db
//显示所有数据库
show dbs
//创建数据库并选择连接到一个指定的数据库(只有插入第一条记录后执行show dbs才显示)
use testDB
//删除当前选择的数据库
db.dropDatabase()

  操作集合

//显示当前库下的所有集合
show collections
db.collections()
//创建capped collections,该集合不能删除只能更新,且大小不能变
db.createCollection("myco", {capped:true, size:10000})
//删除集合
db.tb.drop()
  • 增删改查操作

1、增加数据,语法: db.collectionName.isnert(document)。

#不指定文档的id,数据库会默认分配一个随机id
db.user.insert({name:'zhaos',age:23,sex:'f'});
#指定文档的id
db.user.insert({_id:5,name:'zhaos',age:23,sex:'f'});
#增加单个文档
db.user.insert({_id:6,name:'zhaos',age:23,sex:'f'});
#增加多个文档 
db.user.insert([{_id:2,name:'zhangs',age:21,sex:'m'},{_id:3,name:'wangw',age:22,sex:'m'},{_id:4,name:'zhaos',age:23,sex:'f'}]);

注意,这里解释下自动生成的id,MongoDB采用了一个ObjectId的类型来做主键,ObjectId是一个12字节的 BSON 类型字符串,按照字节顺序,依次代表:

4字节:UNIX时间戳 
3字节:表示运行MongoDB的机器 
2字节:表示生成此_id的进程 
3字节:由一个随机数开始的计数器生成的值 

2、删除数据,语法: db.collection.remove(查询表达式, 选项)。选项是指需要删除的文档数,{0/1},默认是0,删除全部文档。

a. 删除操作比较简单,直接调用remove()方法指定删除的条件即可,符合条件的所有数据均会被删除,示例如下:
result = mongo_conn.remove({'name': 'Kevin'})  # 运行结果: {'ok': 1, 'n': 1}
b. delete_one()和delete_many()方法:
result = mongo_conn.delete_one({'name': 'Kevin'})
result = mongo_conn.delete_many({'age': {'$lt': 25}})
print(result.deleted_count)
# delete_one()即删除第一条符合条件的数据,delete_many()即删除所有符合条件的数据,返回结果是DeleteResult类型,
# 可以调用deleted_count属性获取删除的数据条数。
#将所有_id=7的文档删除
db.user.remove({_id:7})  
#将gender:'m'的所有文档删除
db.user.remove({gender:'m'})
#只删除一个gender:'m'的文档,num是指删除的文档数
db.user.remove({gender:'m',1})
# 删除集合所有数据
db.user.remove({})

3、修改数据,语法: db.collection.update(查询表达式,新值);

a. 对于数据更新可以使用update()方法,指定更新的条件和更新后的数据即可,例如:

condition = {'name': 'Kevin'}

student = mongo_conn.find_one(condition)

student['age'] = 25

result = mongo_conn.update(condition, student)    #运行结果: {'ok': 1, 'nModified': 1, 'n': 1, 'updatedExisting': True}

# 返回结果是字典形式,ok即代表执行成功,nModified代表影响的数据条数。

 

b 另外update()方法其实也是官方不推荐使用的方法,在这里也分了update_one()方法和update_many()方法,用法更加严格,

# 第二个参数需要使用$类型操作符作为字典的键名,我们用示例感受一下。

condition = {'name': 'Kevin'}

student = mongo_conn.find_one(condition)

student['age'] = 26

result = mongo_conn.update_one(condition, {'$set': student}) # 其返回结果是UpdateResult类型,然后调用matched_count和modified_count属性分别可以获得匹配的数据条数和影响的数据条数。

print(result.matched_count, result.modified_count)

# 我们再看一个例子:

condition = {'age': {'$gt': 20}}

result = mongo_conn.update_one(condition, {'$inc': {'age': 1}})  # 在这里我们指定查询条件为年龄大于20,然后更新条件为{'$inc': {'age': 1}},执行之后会将第一条符合条件的数据年龄加1。

# 如果调用update_many()方法,则会将所有符合条件的数据都更新,示例如下:

condition = {'age': {'$gt': 20}}

result = mongo_conn.update_many(condition, {'$inc': {'age': 1}})  # 所有匹配到的数据都会被更新。

c. replace_one()

student = mongo_conn.replace_one({'hp': 1}, {'tel': 1}) #更改存储键名
更新
#从结果可以看出,这只是在替换一个文档,并非修改一个文档字段
> db.user.update({name:'zhangs'},{name:'liul'}) 
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find();
{ "_id" : 2, "name" : "liul" }

#修改一个文档的字段,必须使用$set:{属性:'值'}
> db.user.update({name:'zhaos'},{$set:{name:'kongkong'}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.user.find();
{ "_id" : 6, "name" : "kongkong", "age" : 23, "sex" : "f" }
修改时的赋值表达式
$set 修改某列的值
db.user.update({name:'zhaos'},{$set:{name:'kongkong'}})

$unset 删除某个列
eg:db.user.update({name:'kongkong'},{$unset:{name:'kongkong'}})

$rename 重命名某个列
eg:db.user.update({_id:6},{$rename:{sex:'gender'}})
eg:db.user.update({},{$rename:{'sex':'gender'}},{multi:true})

$inc 增长某个列
eg:db.user.update({_id:6},{$inc:{age:2}})

$setOnInsert 当upsert为true时,并且发生了insert操作时,可以补充的字段.
eq:db.user.update({_id:7},{$setOnInsert:{age:5,gender:'f'}},{upsert:true})

4、查找数据,语法: db.collection.find(查询表达式,查询的列)。

#查询一个表中的所有文档    
db.user.find()

#查询特定属性的文档
db.user.find({_id:3})

#查询所有文档,显示gender列,不显示id
db.user.find({},{gender:1,_id:0})

#查询所有gender:'m'的文档,显示gender列,age列,不显示id
db.user.find({gender:'m'},{gender:1,_id:0,age:1})

5、高级查询知识点。

$lt小于{'age': {'$lt': 20}}

$gt大于{'age': {'$gt': 20}}

$lte小于等于{'age': {'$lte': 20}}

$gte大于等于{'age': {'$gte': 20}}

$ne不等于{'age': {'$ne': 20}}

$in在范围内{'age': {'$in': [20, 23]}}

$nin不在范围内{'age': {'$nin': [20, 23]}}

# 在这里将一些功能符号再归类如下:

$regex匹配正则{'name': {'$regex': '^M.*'}}name以M开头

$exists属性是否存在{'name': {'$exists': True}}name属性存在

$type类型判断{'age': {'$type': 'int'}}age的类型为int

$mod数字模操作{'age': {'$mod': [5, 0]}}年龄模5余0

$text文本查询{'$text': {'$search': 'Mike'}}text类型的属性中包含Mike字符串

$where高级条件查询{'$where': 'obj.fans_count == obj.follows_count'}自身粉丝数等于关注数
要统计查询结果有多少条数据,可以调用count()方法,

a.如统计所有数据条数:

count = mongo_conn.find().count()

b. 或者统计符合某个条件的数据:

count = mongo_conn.find({'age': 20}).count()
计数
a.可以调用sort方法,传入排序的字段及升降序标志即可,

示例如下:

results = mongo_conn.find().sort('name', pymongo.ASCENDING)

b. 偏移,可能想只取某几个元素,在这里可以利用skip()方法偏移几个位置,比如偏移2,就忽略前2个元素,得到第三个及以后的元素。

results = mongo_conn.find().sort('name', pymongo.ASCENDING).skip(2)

c. 另外还可以用limit()方法指定要取的结果个数,示例如下:

results = mongo_conn.find().sort('name', pymongo.ASCENDING).skip(2).limit(2)

# 值得注意的是,在数据库数量非常庞大的时候,如千万、亿级别,最好不要使用大的偏移量来查询数据,很可能会导致内存溢出,可以使用类似find({'_id': {'$gt': ObjectId('593278c815c2602678bb2b8d')}}) 这样的方法来查询,记录好上次查询的_id。
排序
a.查询所有:mongo_conn.find({})

b.单条记录查询:mongo_conn.find_one({'name': 'Mike'})

c.多条记录查询:

# 对于多条数据的查询,我们可以使用find()方法,例如在这里查找年龄为20的数据,示例如下:

results = mongo_conn.find({'age': 20})  # 返回结果是Cursor类型,相当于一个生成器,我们需要遍历取到所有的结果,每一个结果都是字典类型。

for result in results:

    print(result)

d.条件规则查询

# 如果要查询年龄大于20的数据,则写法如下:results = collection.find({'age': {'$gt': 20}})

# 在这里将比较符号归纳如下表:

$lt小于{'age': {'$lt': 20}}

$gt大于{'age': {'$gt': 20}}

$lte小于等于{'age': {'$lte': 20}}

$gte大于等于{'age': {'$gte': 20}}

$ne不等于{'age': {'$ne': 20}}

$in在范围内{'age': {'$in': [20, 23]}}

$nin不在范围内{'age': {'$nin': [20, 23]}}

# 在这里将一些功能符号再归类如下:

$regex匹配正则{'name': {'$regex': '^M.*'}}name以M开头

$exists属性是否存在{'name': {'$exists': True}}name属性存在

$type类型判断{'age': {'$type': 'int'}}age的类型为int

$mod数字模操作{'age': {'$mod': [5, 0]}}年龄模5余0

$text文本查询{'$text': {'$search': 'Mike'}}text类型的属性中包含Mike字符串

$where高级条件查询{'$where': 'obj.fans_count == obj.follows_count'}自身粉丝数等于关注数
查询总结

6. 创建索引

//单字段
db.col.ensureIndex({"title":1})
//多字段
db.col.ensureIndex({"title":1,"content":-1})
//在后台执行创建索引工作
db.values.ensureIndex({"title":1,"content":-1}, {background: true})

7. 组合方法

a.find_one_and_delete
result = mongo_conn.find_one_and_delete({"name":"zheng"})  # 查找第一条并删除
b.find_one_and_replace
result = mongo_conn.find_one_and_replace({'x': 1}, {'y': 1})  # 查找第一条并替换
c.find_one_and_update
result = mongo_conn。find_one_and_update({'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}}) #运行结果: {u'_id': 665, u'done': False, u'count': 25}}

四、Python操作mongodb

# -*- coding: utf-8 -*-

"""
@Datetime: 2019/1/31
@Author: Zhang Yafei
"""
import json
import pymongo
import pandas as pd


class MongoPipeline(object):
    """
    mongodb:
        save(self, data, collection):                    将数据保存到数据库
        read(self, data):                                读取数据库中指定表格
        insert(self, table, dict_data):                 插入数据
        delete(self, table, condition):                 删除指定数据
        update(self, table, condition, new_dict_data):  更新指定数据
        dbFind(self, table, condition=None):             按条件查找
        findAll(self, table):                           查找全部
        close(self):                                    关闭连接
    """

    def __init__(self, mongo_db, mongo_uri='localhost'):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close(self):
        """
        关闭连接
        :return:
        """
        self.client.close()

    def save(self, data, collection):
        """
        将数据保存到数据库表
        :param data:
        :param collection:
        :return: None
        """
        self.collection = self.db[collection]
        try:
            if self.collection.insert(json.loads(data.T.to_json()).values()):
                print('mongodb insert {} sucess.'.format(collection))
                return
        except Exception as e:
            print('insert error:', e)
            import traceback
            traceback.print_exc(e)

    def read(self, table):
        """
        读取数据库中的数据
        :param table:
        :return: dataframe
        """
        try:
            # 连接数据库
            table = self.db[table]
            # 读取数据
            data = pd.DataFrame(list(table.find()))
            return data
        except Exception as e:
            import traceback
            traceback.print_exc(e)

    def insert(self, table, dict_data):
        """
        插入
        :param table:
        :param dict_data:
        :return: None
        """
        try:
            self.db[table].insert(dict_data)
            print("插入成功")
        except Exception as e:
            print(e)

    def update(self,table, condition, new_dict_data):
        """
        更新
        :param table:
        :param dict_data:
        :param new_dict_data:
        :return: None
        """
        try:
            self.db[table].update(condition, new_dict_data)
            print("更新成功")
        except Exception as e:
            print(e)

    def delete(self,table, condition):
        """
        删除
        :param table:
        :param dict_data:
        :return: None
        """
        try:
            self.db[table].remove(condition)
            print("删除成功")
        except Exception as e:
            print(e)

    def dbFind(self, table, condition=None):
        """
        按条件查找
        :param table:
        :param dict_data:
        :return: generator dict
        """
        data = self.db[table].find(condition)
        for item in data:
            yield item

    def findAll(self, table):
        """
        查找全部
        :param table:
        :return: generator dict
        """
        for item in self.db[table].find():
            yield item


if __name__ == '__main__':
    mongo = MongoPipeline('flask')
    # data = mongo.read('label')
    # print(data.head())
    condition = {"药品ID": 509881}
    data = mongo.dbFind('label', condition)
    print(data)
    for i in data:
        print(i)
    # mongo.findAll()