概述
MongoDB 是一個介於關係型資料庫和非關係型資料庫之間的產品,是非關係型資料庫中功能最豐富,最像關係型資料庫的。
MongoDB 支援的資料結構非常鬆散,類似 json 的 bson 格式,因此可以儲存比較複雜的資料型別。MongoDB 最大的特點是支援的查詢語言非常強大,語法類似於物件導向的查詢語言,幾乎可以實現類似關係型資料庫單表查詢的絕大部分功能,還支援對資料建立索引
MongoDB 的特點:
- 面向集合儲存,易儲存物件型別的資料
- 支援查詢,以及動態查詢
- 支援多種語言
- 檔案儲存格式為 BSON
- 支援主從複製、故障恢復和分片
MongoDB 的應用場景:
- 遊戲應用:使用 MongoDB 作為遊戲伺服器的資料庫儲存使用者資訊,使用者的遊戲裝備、積分等直接以內嵌文件的形式儲存,方便查詢和更新
- 物流應用:使用 MongoDB 儲存訂單資訊,訂單狀態在運送過程中會不斷更新,以內嵌陣列的形式儲存,一次查詢就能將訂單所有的變更讀取出來
- 社交應用:使用 MongoDB 儲存使用者資訊以及使用者發表的朋友圈資訊,通過地理位置索引實現附近的人、地點等功能,儲存聊天記錄
- 大資料應用:使用 MongoDB 作為大資料的雲端儲存系統,隨時進行資料提取分析,掌握行業動態
MongoDB 安裝
1. 傳統方式安裝
在官網下載對應版本的安裝包並解壓:https://www.mongodb.com/try/download/communiy
這裡選擇 ubuntu 環境下的 5.0.8 版本
進入 bin 目錄,啟動 MongoDB 服務
./mongod --port=27017 --dbpath ../data --logpath ../logs/mongo.log &
- port:指定服務監聽埠,預設 27017
- dbpath:指定 mongo 的資料存放目錄
- logpath:指定 mongo 的日誌存放目錄
- &:表示程式在後臺執行
使用客戶端連線 MongoDB 服務
# ./mongo [mongodb://[主機名:埠號]]
./mongo mongodb://127.0.0.1:27017
2. Docker 方式安裝
拉取 MongoDB 映象
docker pull mongo:5.0.8
執行 MongoDB 映象
docker run -d --name mongo --p 27017:27017 mongo:5.0.5
進入 MongoDB 容器
docker exec -it [容器id] bash
MongoDB 核心概念
1. 庫
MongoDB 中的庫類似傳統關係型資料庫中庫的概念,用來通過不同的庫隔離不同的資料
MongoDB 中可以建立多個資料庫,每一個庫都有自己的集合和許可權,不同的資料庫也放置在不同的檔案中
2. 集合
集合就是 MongoDB 文件組,類似於關係型資料庫中表的概念
集合儲存在庫中,一個庫可以有多個集合,每個集合沒有固定的結構,這意味著可以對集合插入不同格式和型別的資料,但通常我們插入集合資料都會有一定的關聯性
3. 文件
文件集合中的記錄,是一組鍵值對(BSON)
MongoDB 的文件不需要設定相同的欄位,並且相同的欄位不需要相同的資料型別,這與關係型資料庫有很大的區別,也是 MongoDB 的特點
一個簡單的文件例子:
{"site":"www.google.com", "name":"xiaowang"}
MongoDB 基本操作
1. 庫操作
# 檢視所有庫,預設不顯示沒有集合的庫
show databases | show dbs
# 選中一個庫,如果庫不存在,則自動建立
use [庫名]
# 幫助指令
db.help()
# 檢視當前庫
db
# 刪除當前庫
db.dropDatabase()
MongoDB 有三個保留庫:
- admin:從許可權的角度來看,這是 root 資料庫。如果一個使用者被新增到這個資料庫,這個使用者將自動繼承對所有資料庫的許可權。一些特定的伺服器端命令也只能從這個資料庫執行,比如列出所有的資料庫或者關閉伺服器
- local:該庫的資料永遠不會被複制(例如建立副本),可以用來儲存僅限於本地單臺伺服器的任意集合
- config:當 Mongo 用於分片設定時,config 資料庫在內部使用,用於儲存分片的相關資訊
2. 集合操作
# 檢視當前庫的集合
show collections | show tables
# 顯示建立集合
# db.createCollection("[集合名]", [Options])
db.createCollection("products", {capped:true,size:5000})
# 向集合插入資料/隱式建立集合,向不存在的集合插入資料也可以建立集合
# db.[集合名稱].insert({"[屬性名]":"[屬性值]",...})
db.emp.insert({name:"xiaowang"})
# 刪除集合
# db.[集合名稱].drop()
Options 可以是如下引數:
- capped:(可選)如果為true,則建立固定集合。固定集合是指有著固定大小的集合,當達到最大值時,它會自動覆蓋最早的文件。當該值為 true 時,必須指定 size 引數
- size:(可選)為固定集合指定一個最大值,即位元組數。如果 capped 為 true,也需要指定該欄位
- max:(可選)指定固定集合中包含文件的最大數量
3. 文件操作
# 插入單條文件
db.[集合名稱].insert({"[屬性名]":"[屬性值]",...})
# 插入多條文件
db.[集合名稱].insertMany(
[<document1>,<document2>,...],
{
writeConcern: 1 # 寫入策略,預設為1,表示要求確認寫操作,為0不要求
ordered: true # 指定是否按順序寫入,預設為true,按順序寫入
}
)
db.[集合名稱].insert(
[<document1>,<document2>,...]
)
# 指令碼方式插入多條文件,MongoDB預設會為每一條文件設定一個_id的key
for(var i = 0; i < 10; i++) {
db.[集合名稱].insert({"_id":i, ....})
}
# 查詢文件
# query 可選,指定查詢條件
# projection 可選,指定返回的鍵值,不寫預設返回全部鍵值
# pretty 對返回結果格式化
db.[集合名稱].find(query,project).pretty()
# 使用運算子查詢
# > : ($gt)
# < : ($lt)
# >= : ($gte)
# <= : ($lte)
# = : ($eq)
# != : ($ne)
# 查詢年齡大於29的使用者記錄
db.users.find({age:{$gt:29}})
# AND 查詢
db.[集合名稱].find($and:[{key1:value1,key2:value2,...},...]).pretty()
# OR 查詢
db.[集合名稱].find($or:[{key1:value1,key2:value2,...},...]).pretty()
# and or 聯合
db.[集合名稱].find($and:[...],$or:[...]).pretty()
# not or 查詢,既不是也不是
db.[集合名稱].find($nor:[...]).pretty()
# 模糊查詢
db.[集合名稱].find({查詢欄位:正規表示式})
# 陣列中查詢,找出likes陣列欄位中有看電視值的記錄
db.users.find({likes:"看電視"})
# 陣列中查詢,找出likes陣列欄位長度為3的記錄
db.users.find({likes:{$size:3}})
# 對查詢排序,1升序,2降序
db.[集合名稱].find({查詢條件}).sort({排序欄位:升序/降序,...})
# 對查詢分頁
db.[集合名稱].find({查詢條件}).skip(起始條數).limit(每頁顯示的記錄數)
# 查詢總條數
db.[集合名稱].count()
# 去重
db.[集合名稱].distinct("欄位")
# 文件刪除
db.[集合名稱].remove(
<query>, # 可選,刪除文件的條件
{
justOne: <boolean> # 可選,設為true則只刪除一個文件,否則刪除所有匹配的文件
writeConcern: <document> # 可選,丟擲異常的級別
}
)
# 刪除_id為1的文件
db.users.remove({"_id":1})
# 更新文件
db.[集合名稱].update(
<query>, # 查詢條件
<update>, # 更新操作符,類似sql update的set
{
upsert: <boolean>, # 可選,如果不存在記錄,則插入,預設為true
multi: <boolean>, # 可選,預設false表示只更新第一條記錄,true表示更新符合條件的全部記錄
writeConcern: <document> # 可選,丟擲異常的級別
}
)
# 這種更新相當於先刪除再插入
db.[集合名稱].update({"name":"zhangsan" },{name:"11",bir:new date()})
# 儲存原有資料的更新
db.[集合名稱].update({"name":"xiaohei"},{$set:{name:"mingming"}})
MongoDB 索引
1. 簡介
索引能極大的提高查詢效率。索引是特殊的資料結構,它儲存在一個易於遍歷讀取的資料集合中,是對資料庫表中一列或多列的值進行排序的一種資料結構
MongoDB 索引原理如圖所示:
MongoDB 的索引和其他關係型資料庫中的索引類似,MongoDB 在集合層面上定義了索引,並支援對 MongoDB 集合中的任何欄位或文件的子欄位進行索引
2. 索引操作
# 建立索引,1為指定按升序建立索引,-1為降序
db.[集合名稱].createIndex(keys,options)
db.topics.createIndex({"title":1})
# 建立複合索引,只有使用索引前部的查詢才能使用該索引
db.[集合名稱].createIndex({"[要建立索引的欄位]":1,....})
# 檢視索引
db.[集合名稱].getIndexes()
# 檢視集合索引大小
db.[集合名稱].totalIndexSize()
# 刪除集合所有索引
db.[集合名稱].dropIndexs()
# 刪除集合指定索引
db.[集合名稱].dropIndex("索引欄位")
createIndex 可接受以下可選引數:
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引過程會阻塞其它資料庫操作,background 可指定以後臺方式建立索引,即增加 background 可選引數。 "background" 預設值為 false |
unique | Boolean | 建立的索引是否唯一。指定為 true 建立唯一索引。預設值為 false |
name | string | 索引的名稱。如果未指定,MongoDB 的通過連線索引的欄位名和排序順序生成一個索引名稱 |
dropDups | Boolean | 3.0+ 版本已廢棄。在建立唯一索引時是否刪除重複記錄,指定 true 建立唯一索引。預設值為 false |
sparse | Boolean | 對文件中不存在的欄位資料不啟用索引;這個引數需要特別注意,如果設定為 true 的話,在索引欄位中不會查詢出不包含對應欄位的文件.。預設值為 false |
expireAfterSeconds | integer | 指定一個以秒為單位的數值,完成 TTL 設定,設定集合的生存時間 |
v | index version | 索引的版本號。預設的索引版本取決於 mongod 建立索引時執行的版本 |
weights | document | 索引權重值,數值在 1 到 99,999 之間,表示該索引相對於其他索引欄位的得分權重。 |
default_language | string | 對於文字索引,該引數決定了停用詞及詞幹和詞器的規則的列表。 預設為英語 |
language_override | string | 對於文字索引,該引數指定了包含在文件中的欄位名,語言覆蓋預設的 language,預設值為 language |
SpringBoot 整合 MongoDB
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
編寫配置
# mongodb(協議)://主機:埠/庫名
spring.data.mongodb.uri=mongodb://127.0.0.1:27017/test
# 如果開啟使用者名稱密碼校驗
spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.database=test
spring.data.mongodb.username=root
spring.data.mongodb.password=123
建立和刪除集合
@Test
public void testCollection() {
// 建立集合
mongoTemplate.createCollection("products");
// 刪除集合
mongoTemplate.dropCollection("products");
}
操作文件
@Document("users") // 代表是users集合的文件
public class User {
@Id // 對映文件的_id
private Integer id;
@Field // 對映文件的鍵值對
private String name;
@Field
private Integer salary;
@Field
private Date birthday;
....
}
@Test
public void testDocument() {
User user = new User(1, "hhh", 3000, new Date())
// _id存在時更新資料
mongoTemplate.save(user)
// _id存在時發生主鍵衝突
mongoTemplate.insert(user)
// 批量插入
List<User> users = Arrays.asList(
new User(2, "aaa", 3000, new Date()),
new User(3, "bbb", 3000, new Date())
);
mongoTemplate.insert(users, User.class)
// 基於id查詢
mongoTemplate.findById("1", User.class);
// 查詢所有
mongoTemplate.findAll(User.class);
mongoTemplate.find(new Query(), User.class);
// 等值查詢
mongoTemplate.find(new Query(Criteria.where("name").is("aaa")), User.class);
// > gt < lt >= gte <= lte
mongoTemplate.find(new Query(Criteria.where("age").lt(25)), User.class);
mongoTemplate.find(new Query(Criteria.where("age").gt(25)), User.class);
mongoTemplate.find(new Query(Criteria.where("age").gte(25)), User.class);
mongoTemplate.find(new Query(Criteria.where("age").lte(25)), User.class);
// and 查詢
mongoTemplate.find(new Query(Criteria.where("name").is("aaa").and("salary").is(3000)), User.class);
// or 查詢
mongoTemplate.find(
new Query(
Criteria.orOperator(
Criteria.where("name").is("aaa"),
Criteria.where("name").is("bbb")
)),
User.class);
// and or 查詢
mongoTemplate.find(
new Query(
Criteria.where("salary").is("3000")
.orOperator(
Criteria.where("name").is("aaa")
)),
User.class);
// 排序查詢
mongoTemplate.find(
new Query().with(Sort.by(Sort.Order.desc("salary"))),
User.class);
// 分頁查詢
mongoTemplate.find(
new Query().with(Sort.by(Sort.Order.desc("salary")))
.skip(0)
.limit(2),
User.class);
// 總條數
mongoTemplate.count(new Query(), User.class);
// 去重
mongoTemplate.findDistinct(new Query(), User.class);
// 使用json字串查詢
Query query = new BasicQuery("{name:'aaa'}");
mongoTemplate.find(query, User.class);
// 更新條件
Query query = Query().query(Criteria.where("age").is(23));
// 更新內容
Update update = new Update();
update.set("name", "ccc");
// 單條更新
mongoTemplate.updateFirst(query, update, User);
// 多條更新
mongoTemplate.updateMulti(query, update, User);
// 更新插入
mongoTemplate.upsert(query, update, User);
// 刪除所有
mongoTemplate.remove(new Query, User.class);
// 條件刪除
mongoTemplate.remove(
Query.query(Criteria.where("name").is("aaa"))
, User.class);
}
MongoDB 副本集
1. 簡介
MongoDB 副本集是有自動故障恢復功能的主從叢集,由一個 Primary 節點和一個或多個 Secondary 節點組成。副本叢集沒有固定的主節點。當出現故障時,整個叢集會選舉出一個主節點,保證系統的高可用性
2. 搭建副本集
建立資料目錄
sudo mkdir repl1 repl2 repl3
啟動三個節點
# --replSet 副本集選項 myreplace 副本集名稱/叢集中其他節點的主機和埠號
sudo ./mongod --port 27017 --dbpath ../repl1 --bind_ip 0.0.0.0 --replSet myreplace/[localhost:27018,localhost:27019]
sudo ./mongod --port 27018 --dbpath ../repl2 --bind_ip 0.0.0.0 --replSet myreplace/[localhost:27017,localhost:27019]
sudo ./mongod --port 27019 --dbpath ../repl3 --bind_ip 0.0.0.0 --replSet myreplace/[localhost:27017,localhost:27018]
配置副本集,通過 client 登入到任意一個節點,必須在 mongo 中預設的 admin 庫中做叢集的配置
use admin
# 定義配置資訊
> var config = {
"_id":"myreplace",
members:[
{_id:0,host:"aaa:27017"},
{_id:1,host:"aaa:27018"},
{_id:2,host:"aaa:27019"}]
}
# 初始化副本集
rs.initiate(config)
# 開啟從節點查詢許可權
rs.slaveOk()
# 檢視副本集狀態
rs.status()
MongoDB 分片叢集
1. 簡介
分片是指將資料拆分,將其分散存在不同的機器上,不需要功能強大的大型計算機就可以儲存更多的資料,處理更大的負載
MongoDB 支援自動分片,可以擺脫手動分片的管理困擾。MongoDB 分片的基本思想就是將集合切分成小塊,這些塊分散到若干片裡面,每個片只負責總資料的一部分,應用程式不必知道分片細節
- Shard:用於實際儲存的資料塊,實際生產中一個 Shard Server 角色可以組成一個副本集,防止主機單點故障
- Config Server:配置伺服器儲存叢集的後設資料和相關設定,配置伺服器必須部署為副本集
- Query Remote:分片之前要執行一個路由程式,該程式名為 mongos,這個路由器知道所有資料的存放位置,應用可以連線它來正常傳送請求。路由器知道資料和片的對應關係,能夠轉發請求正確的片上,如果請求有了回應,路由器將收集起來回送給應用
- Shard Key:設定分片時需要在集合中選一個鍵,用該鍵的值作為拆分資料的依據,這個片鍵稱為 shard key
2. 搭建分片叢集
# 1.叢集規劃
Shard Server 1:27017
Shard Repl 1:27018
Shard Server 2:27019
Shard Repl 2:27020
Shard Server 3:27021
Shard Repl 3:27022
Config Server :27023
Conifg Server :27024
Conifg Server :27025
Route Process :27026
# 2.進入安裝的 bin 目錄建立資料目錄
mkdir -p ../cluster/shard/s1
mkdir -p ../cluster/shard/s1-repl
...
mkdir -p ../cluster/shard/config3
# 3.啟動4個shard服務並分別初始化
sudo ./mongod --port 27017 --dbpath ../cluster/shard/s1 --bind_ip 0.0.0.0 --shardsvr --replSet r0/127.0.0.1:27018
sudo ./mongod --port 27018 --dbpath ../cluster/shard/s1 --bind_ip 0.0.0.0 --shardsvr --replSet r0/127.0.0.1:27017
...
# 4.啟動3個config服務並初始化
sudo ./mongod --port 27023 --dbpath ../cluster/shard/config1 --bind_ip 0.0.0.0 --configsvr --replSet r0/[127.0.0.1:27024,127.0.0.1:27025]
...
> var config = {
"_id":"config",
configsvr:true,
members:[
{_id:0,host:"127.0.0.1:27023"},
{_id:1,host:"127.0.0.1:27024"},
{_id:2,host:"127.0.0.1:27025"}
]
}
> rs.initiate(config)
# 5.啟動路由服務
./mongos --port 27026 --configdb config/127.0.0.1:27023,127.0.0.1:27024,127.0.0.1:27025 --bind_ip 0.0.0.0
# 6.登入mongos服務
# 6.1 登入 mongo --port 27026
# 6.2 使用 admin 庫
use admin
# 6.3 新增分片資訊
db.runCommand({addshard:"127.0.0.1:27017","allowLocal":true})
db.runCommand({addshard:"127.0.0.1:27019","allowLocal":true})
db.runCommand({addshard:"127.0.0.1:27021","allowLocal":true})
# 6.4 指定分片的資料庫
db.runCommand({enablesharding:"[庫名]"})
# 6.5 設定庫的片鍵資訊
db.runCommand({shardcollection:"[庫名].[集合名]",key:{[欄位名]:1}})
db.runCommand({shardcollection:"[庫名].[集合名]",key:{[欄位名]:"hashed"}}) # 通過對片鍵雜湊將資料散開