#2 Mongo 基础

2018-09-05

版本

以下是几个大版本和发布时间,作为一个大概的时间线吧:

2009/12 1.2
2010/03 1.4
2010/08 1.6
2011/03 1.8
2011/09 2.0
2012/08 2.2
2013/03 2.4
2014/04 2.6
2015/03 3.0
2015/12 3.2
2016/11 3.4
2017/11 3.6
2018/06 4.0

当前最新版本 8 月发布的 4.0.2

Update @ 2021/06/07:
之后主版本号就一直停在了 4,2020 年之后甚至一直停在了 4.4(2019 年 4.2,2020 年 4.4),这也意味着功能组件稳定下来了。

概念

在 Mongo 中,有一些名词变了,看文档的时候需要注意。

  1. 表 Table -> Collection 集合
  2. 行 Row -> Document 文档
  3. 列 Column -> Field 字段(其实 RDB 中的 Column 也经常说成字段)

数据库 Database,索引 Index 不变。

Collection

Document

  1. BSON
  2. 字段名是字符串类型(UTF-8)
  3. 字段有序
  4. 值的类型:
  5. String UTF-8 类型
  6. Int32
  7. Long
  8. Double
  9. Boolean
  10. Object
  11. Array
  12. Date
  13. ObjectId
  14. Null
  15. Date
  16. Timestamp
  17. Decimal128
  18. MinKey
  19. MaxKey
  20. Binary data
  21. JavaScript

废弃:

  1. Undefined
  2. DBPointer
  3. Symbol
  4. JavaScript code with scope

ObjectID

所有 Document 必须有一个 _id 键,没有类型限制,默认是 ObjectID 类型。
PS:所有 Document(BSON)中还包含一个时间戳,4B 时间戳 + 4B 自增数

  • 4B Unix 时间戳
  • 3B 机器标识符
  • 2B 进程号
  • 3B 随机数

关于 NoSQL

在大数据时代,传统 RDB 十分严谨的同时,效率也十分低下。
有人创造了 NoSQL 这个词,表示 Non-SQL,即不支持 SQL 的数据库。
后来部分产品添加了 SQL 支持,含义又演化成 Not Only SQL。
再后来又有人提出 NewSQL 概念,还是差不多的意思。

实现方式:

  1. 键值存储 Key-value store
  2. 文档存储 Document store
  3. 图数据库 Graph
  4. 对象数据库 Object database
  5. Tabular
  6. Tuple store
  7. Triple/quad store (RDF) database
  8. 主机式服务 Hosted
  9. 多数据库 Multivalue Database
  10. Multimodel Database

MongoDB 解决了什么问题

什么事情是 MongoDB 可以做,而 MySQL / PostgreSQL 不能做的?

特点

  • C++ 开发
  • 文档存储(JSON)
  • No Schema
  • 可以为任意属性建立索引
  • 灵活的查询语法
  • 内置小文件存储(GridFS)
  • 服务器端脚本
  • 分布式
  • 基于 AGPLv3 发布

PS:由于和云服务提供商的斗争,4.1.4 之后的所有版本(还有老版本的 BUG 修复版本)全部改用 SSPL (Server Side Public License,服务器端公共许可证) 。
该协议要求云服务提供商必须使用相同协议开放代码(之前所有主要的开源协议都没有对云服务提供商做任何限制),但 OSI 拒绝承认这是开源协议,部分 GNU/Linux 发行版将其从软件源移除。
再后来,其他开源项目,比如 Redis,Elastic,Graylog,也相继采用了和 MongoDB 相同的策略,改用了新的协议分发代码以对抗云服务提供商。

安装

sudo apt install -y mongodb
sudo systemctl disable mongodb
sudo systemctl restart mongodb
sudo systemctl status mongodb

如果希望直接从官方安装最新版本(仅支持 Ubuntu LTS 版本):

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu/dists/$(lsb_release -cs)/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb44.list
sudo apt update
sudo apt install -y mongodb-org
# sudo apt-mark hold mongodb-org # 避免随系统更新

配置

PS:较老的版本有一个自带的简易 Web UI,通过 httpinterface, 或者 rest 参数可以启动,3.2 之后去掉了。

配置文件

# 自带配置
dbpath=/var/lib/mongodb
logpath=/var/log/mongodb/mongodb.log
logappend=true
bind_ip = 127.0.0.1  # 修改为 0.0.0.0
# 端口是默认的 27017
journal=true

# 添加配置
replSet = markjour
# daemon 模式运行,如果用 systemd 就不需要这一行:
fork = true
sudo systemctl restart mongodb

sudo ps -ef | grep mongod
sudo netstat -antp | grep mongod

副本集

// 主节点初始化
rs.initiate();

// 查看副本集配置
cfg = rs.conf();

// 修改主节点 host 为 IP 形式
cfg.members[0].host = "<IP>:<Port>";
rs.reconfig(cfg);

// 添加从节点
rs.add("192.168.64.234");
// 如果是连接不上的节点:"Quorum check failed because not enough voting nodes responded; required 2 but only the following 1 voting nodes responded: 172.16.0.49:27017; the following nodes did not respond affirmatively: 192.168.64.234:27017 failed with Couldn't get a connection within the time limit"
rs.remove("192.168.64.234"); // 移除节点

使用

客户端 SHELL 是基于 JavaScript 的。

mongo
  • show dbs
  • db
  • use <dbName>
  • db.createCollection("s1", {capped:true, size:100000})

保留数据库名称:admin, config, local

连接字符串

https://www.mongodb.com/docs/manual/reference/connection-string/

mongodb://
[username:password@]
host1[:port1]
[,host2[:port2],...[,hostN[:portN]]]
[
    /[database]
    [?options]
]
  • 复制集(ReplicaSet)
  • replicaSet
  • 连接
  • ssl
  • connectTimeoutMS
  • socketTimeoutMS
  • 连接池
  • maxPoolSize
  • minPoolSize
  • maxIdleTimeMS
  • waitQueueMultiple
  • waitQueueTimeoutMS
  • Write Concern
  • w
  • wtimeoutMS
  • journal
  • Read Concern
  • readConcernLevel
  • Read Preference
  • readPreference
  • readPreferenceTags
  • 认证
  • authSource
  • authMedhanism
  • gssapiServiceName
  • 服务器选择与发现
  • localThresholdMS
  • serverSelectionTimeoutMS
  • serverSelectionTryOnce
  • heartbeatFrequencyMS
  • 其他
  • uuidRepresentation

参考资料与拓展阅读

#1 MongoDB 聚集(数据表)中的 ID

2016-06-01

默认是一个 ObjectId 对象,也可以手动设置。

举个栗子

使用 PyMongo:

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

from pymongo import MongoClient

client = MongoClient()  # 连接到默认主机的默认端口:localhost:27017
db = client.test_db
collection = db.test_collection
collection.insert({"Hu" : "Ang", "Love" : [5, 'Sun', 'Xiu']})
collection.insert({"And" : 20, "Daughter" : True})
collection.insert({"GIRL": ',', "IS": "A GIRL", '_id': 123})

如果是 MongoDB 数据库操作,就应该是这样:

$ mongo
> use test_db
> db.test_collection.insert({"Hu" : "Ang", "Love" : [5, 'Sun', 'Xiu']})
> db.test_collection.insert({"And" : 20, "Daughter" : True})
> db.test_collection.insert({"GIRL": ',', "IS": "A GIRL", '_id': 123})

最后查到的结果显示如下:

> db.test_collection.find()
{ "_id" : ObjectId("5746c0f900e0990cfc600938"), "Love" : [ 5, "Sun", "Xiu" ], "Hu" : "Ang" }
{ "_id" : ObjectId("5746c0f900e0990cfc600939"), "And" : 20, "Daughter" : true }
{ "_id" : 123, "GIRL" : ",", "IS" : "A GIRL" }

_id

如果自己往里面传 _id 的话,要注意唯一性约束,如果里面存在这个 _id 值,那么就会报错:E11000 duplicate key error index

为什么没有采用像其他数据库一样的主键自增机制?

可能是因为 MongoDB 天生的分布式属性,导致其不愿耗费精力来处理自增主键的同步问题。

ObjectId

关于 ObjectId 字段,官方文档中对每个字节所表示内容的说明:

ObjectId is a 12-byte BSON type, constructed using:

  • a 4-byte value representing the seconds since the Unix epoch,
  • a 3-byte machine identifier,
  • a 2-byte process id, and
  • a 3-byte counter, starting with a random value.

ObjectId 占 12 个字节,其中:

  • 第 1、2、3、4 个字节用来存 Unix 时间戳
  • 第 5、6、7 个字节用来存机器标识
  • 第 8、9 个字节用来存客户端进程编号
    时间戳 + 机器标识 + 客户端进程编号 保证 “机器 + 进程 + 时间” 的一致性。
  • 第 10、11、12 个字节用来存随机字符串
    保证同一台机器,同一个客户端进程,在一秒种之内创建的记录的一致性。
    2 *_ (8 _ 3) = 16777216,也就是说,理论上,同一台机器,同一个客户端进程,在一秒种之内可以创建 1677 万多条记录。

举个例子,比如在 ObjectId("5746c0f900e0990cfc600939")5746c0f9 就是时间戳,00e099 就是机器标识,0cfc 就是客户端进程编号,600939 就是随机字符串。

通过这个设计,保证不同机器的 mongod 服务、同一个机器上的不同 mongod 服务进程之间都不出现重复值的情况(可能性极低,如果出现,可能也有后续的处理办法)。

重点:ObjectId 在客户端生成!!!

我个人也觉得 ObjectId 在客户端生成比服务器端要好:

  1. 更加容易根据机器标识 + 进程编号保证记录的唯一性
  2. 将生成 ObjectId 的这一部分计算转移出去,也能略微减轻 MongoDB 服务的计算压力。
  3. 客户端插入记录的时候,自己就知道 ID,不需要服务器端的反馈,针对这个设计可以设计出一些不需要返回的 insert 方法,给服务器减少一些查询带来的压力。

PyMongo 中就是使用 bson.objectid.ObjectId 生成的。可以阅读一下相关代码,了解这个 ID 的生成方法。
PS:比如,在我的 Ubuntu 环境中,代码文件就是 /usr/local/lib/python2.7/dist-packages/bson/objectid.py

参考