示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace MongoDBTest
{
internal class Program
{
private const string MongoDBConnection = "mongodb://localhost:27017/admin";

private static IMongoClient _client = new MongoClient(MongoDBConnection);
private static IMongoDatabase _database = _client.GetDatabase("Test");
private static IMongoCollection<CollectionModel> _collection = _database.GetCollection<CollectionModel>("TestCollection");

private static async Task Main(string[] args)
{
// 添加
await InsertOneAsyncTest();

Console.WriteLine("-----------FindAsyncTest输出---------------");
await FindAsyncTest();

Console.WriteLine("-----------Query输出---------------");
await Query("a");

Console.WriteLine("-----------SkipAndSortAndLimitAndProjections输出---------------");
var request = new PageRequest {PageIndex = 1, PageSize = 2};
await SkipAndSortAndLimitAndProjections(request);

Console.ReadKey();
}

/// <summary>
/// 查询,支持模糊查询
/// </summary>
/// <param name="keyword"></param>
/// <returns></returns>
private static async Task Query(string keyword)
{
FilterDefinitionBuilder<CollectionModel> builder = Builders<CollectionModel>.Filter;
string p = keyword == null ? $".*{Regex.Escape("")}.*" : $".*{Regex.Escape(keyword)}.*";

//1,条件为空
//FilterDefinition<CollectionModel> filter2 = FilterDefinition<CollectionModel>.Empty;

//2,不区分大小写
FilterDefinition<CollectionModel> filter2 = builder.Regex("title", new BsonRegularExpression(new Regex(p, RegexOptions.IgnoreCase)));

// 以下两种写法区分大小写
//FilterDefinition<CollectionModel> filter2 = builder.Regex("title", new BsonRegularExpression(p));
//BsonDocument filter2 = new BsonDocument { { "title", $"/{keyword}/" } };

FindOptions<CollectionModel> options = new FindOptions<CollectionModel>
{
BatchSize = 2,
NoCursorTimeout = false
};
var cursor = await _collection.FindAsync(filter2, options);
var batch = 0;
while (await cursor.MoveNextAsync())
{
batch++;
Console.WriteLine($"Batch: {batch}");
IEnumerable<CollectionModel> documents = cursor.Current;
foreach (CollectionModel document in documents)
{
Console.WriteLine(document.ToJson());
Console.WriteLine();
}
}
}

/// <summary>
/// Skip, Sort, Limit, Projections
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private static async Task SkipAndSortAndLimitAndProjections(PageRequest request)
{
var count = 0;
await _collection.Find(FilterDefinition<CollectionModel>.Empty)
.Skip((request.PageIndex - 1) * request.PageSize)
.Limit(request.PageSize)
//.Sort(Builders<CollectionModel>.Sort.Descending("title"))
//.Sort(Builders<CollectionModel>.Sort.Descending(x => x.title).Ascending(x => x.content))
.Sort("{title: 1}")
.Project(x => new { x.m_id, x.title, x.content, x.userinfo })
.ForEachAsync(
obj =>
{
Console.WriteLine($"S/N: {count}, \t Id: {obj.m_id}, title: {obj.title}, content: {obj.content}, serinfo.name: {obj?.userinfo?.name}");
count++;
});
}

private static async Task InsertOneAsyncTest()
{
CollectionModel model = new CollectionModel();
model.title = "A1";
model.content = "可以测试";
model.userinfo = new UserModel
{
name = "测试",
addressList = new List<AddressModel>
{
new AddressModel { address = "北京" },
new AddressModel { address = "上海" }
}
};
await _collection.InsertOneAsync(model);
}

private static async Task FindAsyncTest()
{
using IAsyncCursor<CollectionModel> cursor = await _collection.FindAsync(new BsonDocument());
while (await cursor.MoveNextAsync())
{
IEnumerable<CollectionModel> batch = cursor.Current;
foreach (CollectionModel document in batch)
{
Console.WriteLine(document.ToJson());
Console.WriteLine();
}
}
}
}
}

公共类

做通用查询的时候,可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/// <summary>
/// 公共页面请求参数
/// </summary>
public class PageRequest
{
public int PageIndex { get; set; }

public int PageSize { get; set; }

public string Order { get; set; }
}

/// <summary>
/// 公用页面参数
/// </summary>
public class ParameterBase
{
public string CollectName { get; set; }

public int PageIndex { get; set; }

public int PageSize { get; set; }
}

/// <summary>
/// 公用页面参数-MongoDB
/// </summary>
public class PageMongoParameter : ParameterBase
{
public BsonDocument Sort { get; }
public BsonDocument Skip { get; }
public BsonDocument Limit { get; }
public BsonDocument Filter2 { get; }

public BsonDocument[] Pipeline { get; set; }

public PageMongoParameter(PageRequest req, string collectName, BsonDocument filter = null,
BsonDocument sort = null, BsonDocument filter2 = null)
{
CollectName = collectName;
PageIndex = req.PageIndex;
PageSize = req.PageSize;
Limit = new BsonDocument { { "$limit", PageSize } };
Skip = new BsonDocument { { "$skip", (PageIndex - 1) * PageSize } };

if (sort == null)
{
if (!string.IsNullOrEmpty(req.Order)) Sort = new BsonDocument { { "$sort", new BsonDocument { { req.Order, 1 } } } };
}
else
{
Sort = sort;
}

if (filter != null && filter2 == null)
{
var dom = filter.GetElement(0);
Filter2 = BsonDocument.Parse(dom.Value.ToJson());
}
Filter2 ??= new BsonDocument();

if (filter == null && Sort == null)
Pipeline = new[] { Skip, Limit };
else
{
if (filter != null && Sort != null)
Pipeline = new[] { filter, Skip, Limit, Sort };
else
Pipeline = filter == null ? new[] { Skip, Limit, Sort } : new[] { filter, Skip, Limit };
}
}
}

简介

作为一个数据库,基本的操作就是 CRUDMongoDBCRUD,不使用 SQL 来写,而是提供了更简单的方式。

BsonDocument方式

BsonDocument 方式,适合能熟练使用 MongoDB Shell 的开发者。MongoDB Driver 提供了完全覆盖 Shell 命令的各种方式,来处理用户的 CRUD 操作。

这种方法自由度很高,可以在不需要知道完整数据集结构的情况下,完成数据库的CRUD操作。

数据映射方式

数据映射是最常用的一种方式。准备好需要处理的数据类,直接把数据类映射到 MongoDB,并对数据集进行 CRUD 操作。

字段映射

ID字段

MongoDB 数据集中存放的数据,称之为文档(Document)。每个文档在存放时,都需要有一个ID,而这个 ID 的名称,固定叫 _id,类型是 MongoDB.Bson.ObjectId

当建立映射时,如果给出 _id 字段,则 MongoDB 会采用这个 ID 做为这个文档的 ID ,如果不给出,MongoDB 会自动添加一个 _id 字段。在使用上是完全一样的。唯一的区别是,如果映射类中不写 _id,则 MongoDB 自动添加 _id 时,会用 ObjectId 作为这个字段的数据类型。

ObjectId 是一个全局唯一的数据。

MongoDB 允许使用其它类型的数据作为 ID,例如:stringintlongGUID 等,但这就需要你自己去保证这些数据不超限并且唯一。

可以在类中修改 _id 名称为别的名称,但需要加一个描述属性 BsonIdBsonId 属性会告诉映射,topic_id 就是这个文档数据的IDMongoDB在保存时,会将这个 topic_id 转成 _id 保存到数据集中。

1
2
3
4
5
6
7
public class CollectionModel
{
[BsonId]
public ObjectId topic_id { get; set; }
public string title { get; set; }
public string content { get; set; }
}

注:在 MongoDB 数据集中,ID 字段的名称固定叫 _id。为了代码的阅读方便,可以在类中改为别的名称,但这不会影响 MongoDB 中存放的 ID 名称。

2. 特殊类型 - Decimal

MongoDB 在早期,是不支持 Decimal 的。直到 MongoDB v3.4 开始,数据库才正式支持 Decimal

所以,如果使用的是 v3.4 以后的版本,可以直接使用,而如果是以前的版本,需要用以下的方式:

1
2
[BsonRepresentation(BsonType.Double, AllowTruncation = true)]
public decimal price { get; set; }

其实就是把 Decimal 通过映射,转为 Double 存储。

类字段

添加两个类 ContactAuthor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Contact
{
public string mobile { get; set; }
}
public class Author
{
public string name { get; set; }
public List<Contact> contacts { get; set; }
}

public class CollectionModel
{
[BsonId]
public ObjectId topic_id { get; set; }
public string title { get; set; }
public string content { get; set; }
public int favor { get; set; }
public Author author { get; set; }
}

/*
调用 代码:
*/
private static async Task Demo()
{
CollectionModel new_item = new CollectionModel()
{
title = "Demo",
content = "Demo content",
favor = 100,
author = new Author
{
name = "WangPlus",
contacts = new List<Contact>(),
}
};

Contact contact_item1 = new Contact()
{
mobile = "13800000000",
};
Contact contact_item2 = new Contact()
{
mobile = "13811111111",
};
new_item.author.contacts.Add(contact_item1);
new_item.author.contacts.Add(contact_item2);

await _collection.InsertOneAsync(new_item);
}

文档结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{ 
"_id" : ObjectId("5ef1e635ce129908a22dfb5e"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
}
}

枚举字段

创建一个枚举 TagEnumeration:

1
2
3
4
5
public enum TagEnumeration
{
CSharp = 1,
Python = 2,
}

加到 CollectionModel 中:

1
2
3
4
5
6
7
8
9
10
public class CollectionModel
{
[BsonId]
public ObjectId topic_id { get; set; }
public string title { get; set; }
public string content { get; set; }
public int favor { get; set; }
public Author author { get; set; }
public TagEnumeration tag { get; set; }
}

Demo代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static async Task Demo()
{
CollectionModel new_item = new CollectionModel()
{
title = "Demo",
content = "Demo content",
favor = 100,
author = new Author
{
name = "WangPlus",
contacts = new List<Contact>(),
},
tag = TagEnumeration.CSharp,
};

Contact contact_item1 = new Contact()
{
mobile = "13800000000",
};
Contact contact_item2 = new Contact()
{
mobile = "13811111111",
};
new_item.author.contacts.Add(contact_item1);
new_item.author.contacts.Add(contact_item2);

await _collection.InsertOneAsync(new_item);
}

保存后的文档:注意,tag 保存了枚举的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ 
"_id" : ObjectId("5ef1eb87cbb6b109031fcc31"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
},
"tag" : NumberInt(1)
}

可以保存枚举的字符串。只要在 CollectionModel 中,tag 声明上加个属性:

1
2
[BsonRepresentation(BsonType.String)]
public TagEnumeration tag { get; set; }

数据会变成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ 
"_id" : ObjectId("5ef1ec448f1d540919d15904"),
"title" : "Demo",
"content" : "Demo content",
"favor" : NumberInt(100),
"author" : {
"name" : "WangPlus",
"contacts" : [
{
"mobile" : "13800000000"
},
{
"mobile" : "13811111111"
}
]
},
"tag" : "CSharp"
}

日期字段

CollectionModel 中增加一个时间字段:

1
public DateTime post_time { get; set; }

MongoDBdatetime 存储的是 unixtimestamp ,所以默认只能是 utc0 时区的,它问题出在 C#DateTime 对时区的处理上遗留的问题,可以换成 DateTimeOffset

如果只是保存(像上边这样),或者查询时使用时间作为条件(例如查询 post_time < DateTime.Now 的数据)时,是可以使用的,不会出现问题。

但是,如果是查询结果中有时间字段,那这个字段,会被 DateTime 默认设置为 DateTimeKind.Unspecified 类型。而这个类型,是无时区信息的,输出显示时,会造成混乱。

为了避免这种情况,在进行时间字段的映射时,需要加上属性:

1
2
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime post_time { get; set; }

这样做,会强制 DateTime 类型的字段为 DateTimeKind.Local 类型。这时候,从显示到使用就正确了。

数据集中存放的是 UTC 时间,跟我们正常的时间有8小时时差,如果我们需要按日统计,比方每天的销售额/点击量。

按年月日时分秒拆开存放

1
2
3
4
5
6
7
8
9
class Post_Time
{
public int year { get; set; }
public int month { get; set; }
public int day { get; set; }
public int hour { get; set; }
public int minute { get; set; }
public int second { get; set; }
}

MyDateTimeSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyDateTimeSerializer : DateTimeSerializer
{
public override DateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var obj = base.Deserialize(context, args);
return new DateTime(obj.Ticks, DateTimeKind.Unspecified);
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, DateTime value)
{
var utcValue = new DateTime(value.Ticks, DateTimeKind.Utc);
base.Serialize(context, args, utcValue);
}
}

注意:使用这个方法,一定不要添加时间的属性 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]

对某个特定映射的特定字段使用,比方只对 CollectionModelpost_time 字段来使用,可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[BsonSerializer(typeof(MyDateTimeSerializer))]
public DateTime post_time { get; set; }

//或者全局使用:

BsonSerializer.RegisterSerializer(typeof(DateTime), new MongoDBDateTimeSerializer());

// BsonSerializer是 MongoDB.Driver 的全局对象,可以放到使用数据库前的任何地方。例如在Demo中,放在Main里:
static async Task Main(string[] args)
{
BsonSerializer.RegisterSerializer(typeof(DateTime), new MyDateTimeSerializer());

await Demo();
Console.ReadKey();
}

Dictionary字段

数据声明很简单:

public Dictionary<string, int> extra_info { get; set; }

MongoDB 定义了三种保存属性:DocumentArrayOfDocumentsArrayOfArrays,默认是 Document

属性写法是这样的:

1
2
[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<string, int> extra_info { get; set; }

这三种属性下,保存在数据集中的数据结构有区别。多数情况用 DictionaryRepresentation.ArrayOfDocuments

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// DictionaryRepresentation.Document:
{
"extra_info" : {
"type" : NumberInt(1),
"mode" : NumberInt(2)
}
}


// DictionaryRepresentation.ArrayOfDocuments:
{
"extra_info" : [
{
"k" : "type",
"v" : NumberInt(1)
},
{
"k" : "mode",
"v" : NumberInt(2)
}
]
}

// DictionaryRepresentation.ArrayOfArrays:
{
"extra_info" : [
[
"type",
NumberInt(1)
],
[
"mode",
NumberInt(2)
]
]
}

这三种方式,从数据保存上并没有什么区别,但从查询来讲,如果这个字段需要进行查询,那三种方式区别很大。

如果采用 BsonDocument 方式查询,DictionaryRepresentation.Document 无疑是写着最方便的。

如果用 Builder 方式查询,DictionaryRepresentation.ArrayOfDocuments 是最容易写的。

DictionaryRepresentation.ArrayOfArrays 就算了。数组套数组,查询条件写死人。

BsonElement属性

用来改数据集中的字段名称。

1
2
[BsonElement("pt")]
public DateTime post_time { get; set; }

在不加 BsonElement 的情况下,通过数据映射写到数据集中的文档,字段名就是变量名,上面这个例子,字段名就是 post_time

加上 BsonElement 后,数据集中的字段名会变为 pt

BsonDefaultValue属性

设置字段的默认值。
当写入的时候,如果映射中不传入值,则数据库会把这个默认值存到数据集中。

1
2
[BsonDefaultValue("This is a default title")]
public string title { get; set; }

BsonRepresentation属性

用来在映射类中的数据类型和数据集中的数据类型做转换。

1
2
[BsonRepresentation(BsonType.String)]
public int favor { get; set; }

表示,在映射类中,favor 字段是 int 类型的,而存到数据集中,会保存为 string 类型。

前边 Decimal 转换和枚举转换,就是用的这个属性。

BsonIgnore属性

用来忽略某些字段。
忽略的意思是:映射类中某些字段,不希望被保存到数据集中。

1
2
[BsonIgnore]
public string ignore_string { get; set; }

这样,在保存数据时,字段 ignore_string 就不会被保存到数据集中。

参考:

MongoDB via Dotnet Core数据映射详解

默认是没有 categoriestags 的:

1
2
hexo new page "tags"
hexo new page "categories"

编辑 /tags/index.md

添加:

1
2
type: tags
layout: tags

/categories/index.md

添加:

1
2
type: categories
layout: categories

  • 一月 January,缩写 Jan
  • 二月 February,缩写 Feb
  • 三月 March,缩写 Mar
  • 四月 April,缩写 Apr
  • 五月 May,缩写 May
  • 六月 June,缩写 Jun
  • 七月 July,缩写 Jul
  • 八月 August,缩写 Aug
  • 九月 September,缩写 Sep/Sept
  • 十月 October,缩写 Oct
  • 十一月 November,缩写 Nov
  • 十二月 December,缩写 Dec

常见文件注解

linux系统文件通常在 /var/log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 系统启动后的信息和错误日志
/var/log/message

# 与安全相关的日志信息
/var/log/secure

# 与邮件相关的日志信息
/var/log/maillog

# 与定时任务相关的日志信息
/var/log/cron

# 与UUCP和news设备相关的日志信息
/var/log/spooler

# 守护进程启动和停止相关的日志消息
/var/log/boot.log

# 永久记录每个用户登录、注销及系统的启动、停机的事件
/var/log/wtmp

# 记录当前正在登录系统的用户信息
/var/run/utmp

# 记录失败的登录尝试信息
/var/log/btmp

历史记录

1
2
3
4
5
6
7
8
# 查看重启的命令
last | grep reboot

# 历史操作
history

# 立即清空当前所有的历史记录
history -c

最近重启记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看所有重启日志信息
last reboot

# 查看最近的一条
last reboot | head -1

last -x|grep shutdown | head -1

# -x:显示系统关机和运行等级改变信息
last
last -x
last -x reboot
last -x shutdown

# 开机时间
uptime -s

参考:

linux系统重启 查看日志及历史记录

ObjectId

ObjectId 是一个12字节 BSON 类型数据,有以下格式:

  • 前4个字节表示时间戳
  • 接下来的3个字节是机器标识码
  • 紧接的两个字节由进程id组成(PID)
  • 最后三个字节是随机数。

MongoDB 中存储的文档必须有一个 _id 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象。

在一个集合里面,每个文档都有唯一的 _id 值,来确保集合里面每个文档都能被唯一标识。

MongoDB 采用 ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个服务器上同步自动增加主键值既费力还费时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1.创建新的ObjectId
newObjectId = ObjectId()
# 输出
ObjectId("5f6d62d8b7424d4010318ac6")

# 也可以使用生成的id来取代MongoDB自动生成的ObjectId:
myObjectId = ObjectId("5f6d62d8b7424d4010318ac6")

# 2.创建文档的时间戳
# 由于 ObjectId 中存储了4个字节的时间戳,所以你不需要为你的文档保存时间戳字段,
# 可以通过 getTimestamp 函数来获取文档的创建时间:
ObjectId("5f6d62d8b7424d4010318ac6").getTimestamp()
# 输出
ISODate("2020-09-25T03:24:08Z")

# 3.ObjectId 转换为字符串
new ObjectId().str
# 输出
5f6d6374b7424d4010318ac7

文档关系

MongoDB 中的关系分为:嵌入式关系引用式关系

嵌入式关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"phone": "123345345",
"name": "test",
"address": [
{
"pincode": 123456,
"city": "beijing",
"state": "California"
},
{
"pincode": 456789,
"city": "shanghai",
"state": "Illinois"
}]
}

查询:

db.users.findOne({"name":"test"},{"address":1})

这种数据结构的缺点是,如果用户和用户地址在不断增加,数据量不断变大,会影响读写性能。

引用式关系

1
2
3
4
5
6
7
8
9
10
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"contact": "987654321",
"dob": "01-01-1991",
"name": "test",
"address_ids": [
ObjectId("52ffc4a5d85242602e000000"),
ObjectId("52ffc4a5d85242602e000001")
]
}

查询:

1
2
3
4
5
6
var result = db.users.findOne({"name":"test"},{"address_ids":1})
var addresses = db.address.find({"_id":{"$in":result["address_ids"]}})

# 或
var result = db.users.find({"name":"test"},{"address_ids":1})
var addresses = db.address.find({"_id":{"$in":result[0]["address_ids"]}})

注意:find 返回的数据类型是数组,findOne 返回的数据类型是对象。

引用关系

MongoDB 引用有两种:

  • 手动引用(Manual References)
  • DBRefs

DBRefs

1
2
3
4
# $ref:集合名称
# $id:引用的id
# $db:数据库名称,可选参数
{ $ref : , $id : , $db : }

实例:

1
2
3
4
5
6
7
8
9
10
{
"_id":ObjectId("53402597d852426020000002"),
"address": {
"$ref": "address_home",
"$id": ObjectId("534009e4d852427820000002"),
"$db": "runoob"},
"contact": "987654321",
"dob": "01-01-1991",
"name": "test1"
}

查询:

1
2
3
4
5
6
7
var user = db.users.findOne({"name":"test1"})
var dbRef = user.address

db[dbRef.$ref].findOne({"_id":(dbRef.$id)})

# MongoDB4.0 版本写法
db[dbRef.$ref].findOne({"_id":ObjectId(dbRef.$id)})

覆盖索引查询

官方的 MongoDB 的文档中说明,覆盖查询是以下的查询:

  • 所有的查询字段是索引的一部分
  • 所有的查询返回字段在同一个索引中

由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。
因为索引存在于 RAM 中,从索引中获取数据比通过扫描文档读取数据要快得多。

使用覆盖索引查询

实例:

1
2
3
4
5
6
7
8
{
"_id": ObjectId("53402597d852426020000002"),
"contact": "987654321",
"dob": "01-01-1991",
"gender": "M",
"name": "test1",
"user_name": "tombenzamin"
}

users 集合中创建联合索引,字段为 genderuser_name :

db.users.createIndex({gender:1,user_name:1})

该索引会覆盖以下查询:

db.users.find({gender:"M"},{user_name:1,_id:0})

上述查询,MongoDB 的不会去数据库文件中查找。它会从索引中提取数据,这是非常快速的数据查询。

由于索引中不包括 _id 字段,_id 在查询中会默认返回,我们可以在 MongoDB 的查询结果集中排除它。

下面的实例没有排除 _id,查询就不会被覆盖:

db.users.find({gender:"M"},{user_name:1})

最后,如果是以下的查询,不能使用覆盖索引查询:

  • 所有索引字段是一个数组
  • 所有索引字段是一个子文档

查询分析

MongoDB 查询分析常用函数有:explain()hint()

explain()

explain 操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。

1
2
3
4
5
# users 集合中创建 gender 和 user_name 的索引:
db.users.createIndex({gender:1,user_name:1})

# 在查询语句中使用 explain :
db.users.find({gender:"M"},{user_name:1,_id:0}).explain()

explain() 查询返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"cursor" : "BtreeCursor gender_1_user_name_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 0,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 0,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : true,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"gender" : [
[
"M",
"M"
]
],
"user_name" : [
[
{
"$minElement" : 1
},
{
"$maxElement" : 1
}
]
]
}
}

结果集的字段:

  • indexOnly: 字段为 true ,表示我们使用了索引。
  • cursor:因为这个查询使用了索引,MongoDB 中索引存储在B树结构中,所以这是也使用了 BtreeCursor 类型的游标。如果没有使用索引,游标的类型是 BasicCursor。这个键还会给出你所使用的索引的名称,你通过这个名称可以查看当前数据库下的 system.indexes 集合(系统自动创建,由于存储索引信息,这个稍微会提到)来得到索引的详细信息。
  • n:当前查询返回的文档数量。
  • nscanned/nscannedObjects:表明当前这次查询一共扫描了集合中多少个文档,我们的目的是,让这个数值和返回文档的数量越接近越好。
  • millis:当前查询所需时间,毫秒数。
  • indexBounds:当前查询具体使用的索引。

hint()

hint() 来强制 MongoDB 使用一个指定的索引。

1
2
3
4
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1})

# 可以使用 explain() 函数来分析以上查询:
db.users.find({gender:"M"},{user_name:1,_id:0}).hint({gender:1,user_name:1}).explain()

原子操作

MongoDB 提供了许多原子操作,比如文档的保存,修改,删除等,都是原子操作。

所谓原子操作就是要么这个文档保存到 MongoDB,要么没有保存到 MongoDB,不会出现查询到的文档没有保存完整的情况。

db.collection.findAndModify() 方法来判断书籍是否可结算并更新新的结算信息。

1
2
3
4
5
6
7
8
9
10
db.books.findAndModify ( {
query: {
_id: 123456789,
available: { $gt: 0 }
},
update: {
$inc: { available: -1 },
$push: { checkout: { by: "abc", date: new Date() } }
}
} )

原子操作常用命令

$set

用来指定一个键并更新键值,若键不存在并创建。

{ $set : { field : value } }

$unset

用来删除一个键。

{ $unset : { field : 1} }

$inc

$inc可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作。

{ $inc : { field : value } }

$push

把value追加到field里面去,field一定要是数组类型才行,如果field不存在,会新增一个数组类型加进去。

{ $push : { field : value } }

$pushAll

$push,只是一次可以追加多个值到一个数组字段内。

{ $pushAll : { field : value_array } }

$pull

从数组field内删除一个等于value值。

{ $pull : { field : _value } }

$addToSet

增加一个值到数组内,而且只有当这个值不在数组内才增加。

$pop

删除数组的第一个或最后一个元素

{ $pop : { field : 1 } }

$rename

修改字段名称

{ $rename : { old_field_name : new_field_name } }

$bit

位操作,integer类型

{$bit : { field : {and : 5}}}

偏移操作符

1
2
3
4
5
6
t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }

# 重点
t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )

t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }

高级索引

实例 文档集合(users)包含了 address 子文档和 tags 数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"address": {
"city": "Los Angeles",
"state": "California",
"pincode": "123"
},
"tags": [
"music",
"cricket",
"blogs"
],
"name": "Tom Benzamin"
}

索引数组字段

在数组中创建索引,需要对数组中的每个字段依次建立索引。所以在我们为数组 tags 创建索引时,会为 music、cricket、blogs 三个值建立单独的索引。

1
2
3
4
5
6
7
8
9
10
# 创建数组索引:

db.users.createIndex({"tags":1})

# 创建索引后,我们可以这样检索集合的 tags 字段:
db.users.find({tags:"cricket"})

# 为了验证我们使用使用了索引,可以使用 explain 命令:
# 执行结果中会显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引。
db.users.find({tags:"cricket"}).explain()

索引子文档字段

1
2
3
4
5
6
7
8
9
10
11
# 为子文档的三个字段创建索引,命令如下:
db.users.createIndex({"address.city":1,"address.state":1,"address.pincode":1})

# 一旦创建索引,我们可以使用子文档的字段来检索数据:
db.users.find({"address.city":"Los Angeles"})

# 查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化。所以上面创建的索引将支持以下查询:
db.users.find({"address.state":"California","address.city":"Los Angeles"})

# 同样支持以下查询:
db.users.find({"address.city":"Los Angeles","address.state":"California","address.pincode":"123"})

索引限制

额外开销

每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。

内存(RAM)使用

由于索引是存储在内存( RAM )中,你应该确保该索引的大小不超过内存的限制。

如果索引的大小大于内存的限制,MongoDB 会删除一些索引,这将导致性能下降。

查询限制

索引不能被以下的查询使用:

正则表达式及非操作符,如 $nin, $not, 等。
算术运算符,如 $mod, 等。
$where 子句
所以,检测你的语句是否使用索引是一个好的习惯,可以用 explain 来查看。

索引键限制

从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB 中不会创建索引。

插入文档超过索引键限制

如果文档的索引字段值超过了索引键的限制,MongoDB 不会将任何文档转换成索引的集合。与 mongorestoremongoimport 工具类似。

最大范围

集合中索引不能超过64个
索引名的长度不能超过128个字符
一个复合索引最多可以有31个字段

Map Reduce

Map-Reduce 是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

MongoDB 提供的 Map-Reduce 非常灵活,对于大规模数据分析也相当实用。

MapReduce 命令基本语法

1
2
3
4
5
6
7
8
9
10
db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: collection,
query: document,
sort: document,
limit: number
}
)

使用 MapReduce 要实现两个函数 Map 函数和 Reduce 函数,Map 函数调用 emit(key, value), 遍历 collection 中所有的记录, 将 keyvalue 传递给 Reduce 函数进行处理。

Map 函数必须调用 emit(key, value) 返回键值对。

参数说明:

  • map :映射函数 (生成键值对序列,作为 reduce 函数参数)。
  • reduce 统计函数,reduce 函数的任务就是将 key-values 变成 key-value ,也就是把 values 数组变成一个单一的值 value。。
  • out 统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。
  • query 一个筛选条件,只有满足条件的文档才会调用 map 函数。( query,limitsort可以随意组合)
  • sortlimit 结合的 sort 排序参数(也是在发往 map 函数前给文档排序),可以优化分组机制
  • limit 发往 map 函数的文档数量的上限(要是没有 limit,单独使用 sort 的用处不大)

以下实例在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 来分组,并计算 amount 的总和。

map-reduce.bakedsvg.svg

使用MapReduce

posts 集合中使用 mapReduce 函数来选取已发布的文章(status:”active”),并通过 user_name 分组,计算每个用户的文章数:

1
2
3
4
5
6
7
8
9
10
11
12
13
db.posts.mapReduce( 
function() { emit(this.user_name,1); },
function(key, values) {return Array.sum(values)},
{
query:{status:"active"},
out:"post_total"
}
)
db.post_total.find()

# 输出
{ "_id" : "runoob", "value" : 1 }
{ "_id" : "mark", "value" : 4 }

全文检索

全文检索对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。MongoDB 从 2.4 版本开始支持全文检索,MongoDB 从3.2 版本以后添加了对中文索引的支持。

启用全文检索

MongoDB 在 2.6 版本以后是默认开启全文检索的,如果你使用之前的版本,你需要使用以下代码来启用全文检索:

1
2
3
4
db.adminCommand({setParameter:true,textSearchEnabled:true})

# 或者使用命令:
mongod --setParameter textSearchEnabled=true

创建全文索引

实例:posts 集合的文档数据,包含了文章内容(post_text)及标签(tags):

1
2
3
4
5
6
7
{
"post_text": "enjoy the mongodb articles on Runoob",
"tags": [
"mongodb",
"runoob"
]
}

对 post_text 字段建立全文索引,这样我们可以搜索文章内的内容:

db.posts.createIndex({post_text:"text"})

使用全文索引

1
2
3
4
5
6
7
8
db.posts.find({$text:{$search:"runoob"}})

# 结果:
{
"_id" : ObjectId("53493d14d852429c10000002"),
"post_text" : "enjoy the mongodb articles on Runoob",
"tags" : [ "mongodb", "runoob" ]
}

旧版本的 MongoDB,可以使用以下命令:

db.posts.runCommand("text",{search:"runoob"})

使用全文索引可以提高搜索效率。

删除全文索引

1
2
3
4
5
# 删除已存在的全文索引,可以使用 find 命令查找索引名:
db.posts.getIndexes()

# 执行以下命令来删除索引:
db.posts.dropIndex("post_text_text")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建物理卷
pvcreate /dev/vdb

# 扩展卷组
vgextend centos /dev/vdb

# 扩展逻辑卷大小
lvextend -L +500G /dev/mapper/centos-root
# 或 将卷组 100% 分配到 逻辑卷unicomvol
lvextend -l 100%VG /dev/mapper/centos-root

# 确认文件系统是xfs
cat /etc/fstab | grep centos-root

# xfs_growfs: 调整一个 xfs 文件系统大小(只能扩展)
xfs_growfs /dev/mapper/centos-root

# 可选,resize2fs
# resize2fs -p /dev/mapper/centos-root

# 查看
df -hT

xfs 相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
xfs_admin: 调整 xfs 文件系统的各种参数
xfs_copy: 拷贝 xfs 文件系统的内容到一个或多个目标系统(并行方式) 
xfs_db: 调试或检测 xfs 文件系统(查看文件系统碎片等) 
xfs_check: 检测 xfs 文件系统的完整性 
xfs_bmap: 查看一个文件的块映射 
xfs_repair: 尝试修复受损的 xfs 文件系统 
xfs_fsr: 碎片整理 
xfs_quota: 管理 xfs 文件系统的磁盘配额 
xfs_metadump: 将 xfs 文件系统的元数据 (metadata) 拷贝到一个文件中 
xfs_mdrestore: 从一个文件中将元数据 (metadata) 恢复到 xfs 文件系统 
xfs_growfs: 调整一个 xfs 文件系统大小(只能扩展) 
xfs_freeze    暂停(-f)和恢复(-u)xfs 文件系统
xfs_logprint: 打印xfs文件系统的日志 
xfs_mkfile: 创建xfs文件系统 
xfs_info: 查询文件系统详细信息 
xfs_ncheck: generate pathnames from i-numbers for XFS 
xfs_rtcp: XFS实时拷贝命令 
xfs_io: 调试xfs I/O路径

resize2fs 相关

此命令的适用范围:RedHat、RHEL、Ubuntu、CentOS、SUSE、openSUSE、Fedora

调整 ext2\ext3\ext4 文件系统的大小,它可以放大或者缩小没有挂载的文件系统的大小。如果文件系统已经挂载,它可以扩大文件系统的大小,前提是内核支持在线调整大小。

size 参数指定所请求的文件系统的新大小。如果没有指定任何单元,那么 size 参数的单位应该是文件系统的文件系统块大小。size 参数可以由下列单位编号之一后缀:sKMG,分别用于512字节扇区、千字节、兆字节或千兆字节。文件系统的大小可能永远不会大于分区的大小。如果未指定 Size 参数,则它将默认为分区的大小。

resize2fs 程序不操作分区的大小。如果希望扩大文件系统,必须首先确保可以扩展基础分区的大小。如果您使用逻辑卷管理器 LVM(8) ,可以使用 fdisk(8) 删除分区并以更大的大小重新创建它,或者使用 lvexport(8) 。在重新创建分区时,请确保使用与以前相同的启动磁盘圆柱来创建分区!否则,调整大小操作肯定无法工作,您可能会丢失整个文件系统。运行 fdisk(8) 后,运行 resize2fs 来调整 ext 2文件系统的大小,以使用新扩大的分区中的所有空间。

如果希望缩小 ext2 分区,请首先使用 resize2fs 缩小文件系统的大小。然后可以使用 fdisk(8) 缩小分区的大小。缩小分区大小时,请确保不使其小于 ext2 文件系统的新大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 语法相关
resize2fs [选项] device [size]
resize2fs [ -fFpPM ] [ -d debug-flags ] [ -S RAID-stride ] device [ size ]

# 选项列表
-d debug-flags
打开各种resize2fs调试特性,如果它们已经编译成二进制文件的话。调试标志应该通过从以下列表中添加所需功能的数量来计算:
2,调试块重定位。
4,调试iNode重定位。
8,调试移动inode表。
-f 强制执行,覆盖一些通常强制执行的安全检查。
-F 执行之前,刷新文件系统的缓冲区
-M 将文件系统缩小到最小值
-p 显示已经完成任务的百分比
-P 显示文件系统的最小值
-S RAID-stride

resize2fs 程序将启发式地确定在创建文件系统时指定的 RAID 步长。此选项允许用户显式地指定 RAID 步长设置,以便由 resize2fs 代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看防火状态
# CentOS7
systemctl status firewalld
# Red hat
service iptables status

# 暂时关闭防火墙
# CentOS7
systemctl stop firewalld
# Red hat
service iptables stop

# 重启防火墙
# CentOS7
systemctl enable firewalld
# Red hat
service iptables restart

# 永久关闭防火墙
# CentOS7
systemctl disable firewalld
# Red hat
chkconfig iptables off

MongoDB 连接

标准 URI 连接语法:

1
mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
  • mongodb:// 这是固定的格式,必须要指定。

  • username:password@ 可选项,如果设置,在连接数据库服务器之后,驱动都会尝试登录这个数据库

  • host1 必须的指定至少一个host, host1 是这个URI唯一要填写的。它指定了要连接服务器的地址。如果要连接复制集,请指定多个主机地址。

  • portX 可选的指定端口,如果不填,默认为27017

  • /database 如果指定username:password@,连接并验证登录指定数据库。若不指定,默认打开 test 数据库。

  • ?options 是连接选项。如果不使用 /database,则前面需要加上/。所有连接选项都是键值对name=value,键值对之间通过&或;(分号)隔开

标准的连接格式包含了多个选项(options):

微信截图_20200922163649.png

创建数据库

语法格式

MongoDB 创建数据库的语法格式如下:

1
use DATABASE_NAME

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> use test1
switched to db test1
> db
test1
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
> db.test1.insert({"name":"test1"})
WriteResult({ "nInserted" : 1 })
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
test1 0.000GB

如果数据库不存在,则创建数据库,否则切换到指定数据库。

ongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。

注意: 在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。

删除数据库

语法格式

MongoDB 删除数据库的语法格式如下:

1
db.dropDatabase()

删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。

实例

1
2
3
4
5
6
7
8
> use test1
switched to db test1
> db.dropDatabase()
{ "dropped" : "test1", "ok" : 1 }
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB

创建集合

语法格式

1
db.createCollection(name, options)

参数说明:

  • name: 要创建的集合名称
  • options: 可选参数, 指定有关内存大小及索引的选项

options 可以是如下参数

微信截图_20200922165116.png

在插入文档时,MongoDB 首先检查固定集合的 size 字段,然后检查 max 字段。

在 MongoDB 中,你不需要创建集合。当你插入一些文档时,MongoDB 会自动创建集合。

可以使用 show collectionsshow tables 命令查看已有集合。

删除集合

语法格式:

1
db.collection.drop()

如果成功删除选定集合,则 drop() 方法返回 true,否则返回 false。

1
2
3
4
> db.createCollection("test1")
{ "ok" : 1 }
> db.test1.drop()
true

插入文档

所有存储在集合中的数据都是 BSON 格式。

BSON 是一种类似 JSON 的二进制形式的存储格式,是 Binary JSON 的简称。

1
2
3
4
5
# 若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
db.COLLECTION_NAME.insert(document)

# 该方法新版本中已废弃
db.COLLECTION_NAME.save(document)

3.2 版本之后新增insertOne,insertMany

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 参数说明:
# document:要写入的文档。
# writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
# ordered:指定是否按顺序写入,默认 true,按顺序写入。

# 向集合插入一个新文档
db.collection.insertOne(
<document>,
{
writeConcern: <document>
}
)

# 向集合插入一个多个文档
db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)

异常级别

  • WriteConcern.NONE:没有异常抛出
  • WriteConcern.NORMAL:仅抛出网络错误异常,没有服务器错误异常
  • WriteConcern.SAFE:抛出网络错误异常、服务器错误异常;并等待服务器完成写操作。
  • WriteConcern.MAJORITY: 抛出网络错误异常、服务器错误异常;并等待一个主服务器完成写操作。
  • WriteConcern.FSYNC_SAFE: 抛出网络错误异常、服务器错误异常;写操作等待服务器将数据刷新到磁盘。
  • WriteConcern.JOURNAL_SAFE:抛出网络错误异常、服务器错误异常;写操作等待服务器提交到磁盘的日志文件。
  • WriteConcern.REPLICAS_SAFE:抛出网络错误异常、服务器错误异常;等待至少2台服务器完成写操作。

更新文档

update() 方法

语法格式

1
2
3
4
5
6
7
8
9
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)

参数说明:

  • query : update的查询条件,类似sql update查询内where后面的。
  • update : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的
  • upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
  • multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
  • writeConcern :可选,抛出异常的级别。

实例

1
2
3
4
5
6
db.test1.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}})
db.test1.find().pretty()

# 以上语句只会修改第一条发现的文档,如果你要修改多条相同的文档,
# 则需要设置 multi 参数为 true。
db.test1.update({'title':'MongoDB 教程'},{$set:{'title':'MongoDB'}},{multi:true})

save() 方法

save() 方法通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入。

语法格式如下:

1
2
3
4
5
6
db.collection.save(
<document>,
{
writeConcern: <document>
}
)

参数说明:

  • document : 文档数据。
  • writeConcern :可选,抛出异常的级别。

在3.2版本开始,MongoDB提供以下更新集合文档的方法:

1
2
3
4
5
6
7
8
9
10
11
12
# 向指定集合更新单个文档
db.collection.updateOne()

# 向指定集合更新多个文档
db.collection.updateMany()

# 移除集合中的键值对,使用的 $unset 操作符:
db.col.update({"_id":"56064f89ade2f21f36b03136"}, {$set:{ "test2" : "OK"}})

db.col.update({"_id":"56064f89ade2f21f36b03136"}, {$unset:{ "test2" : "OK"}})


删除文档

MongoDB 最早删除文档使用的是 remove() 方法,后来官方把它移除了因为 remove() 并不会真正释放空间。需要继续执行 db.repairDatabase() 来回收磁盘空间。

建议在执行删除文档函数前先执行 find() 命令来判断执行的条件是否正确 db.test1.find()

1
2
3
db.repairDatabase()
# 或者
db.runCommand({repairDatabase: 1})

官方推荐使用 deleteOne()deleteMany() 方法。

语法

deleteOne() 语法:

1
2
3
4
5
6
7
db.collection.deleteOne(
<filter>,
{
writeConcern: <document>,
collation: <document>
}
)

deleteMany() 语法:

1
2
3
4
5
6
7
db.collection.deleteMany(
<filter>,
{
writeConcern: <document>,
collation: <document>
}
)
1
2
3
4
5
# 删除集合下全部文档
db.inventory.deleteMany({})

# 删除 status 等于 A 的全部文档
db.inventory.deleteMany({ status : "A" })

查询文档

MongoDB 查询文档使用 find(), findOne() 方法。

find() 方法以非结构化的方式来显示所有文档。

语法格式

db.collection.find(query, projection)

  • query :可选,使用查询操作符指定查询条件
  • projection :可选,使用投影操作符指定返回的键。若不指定 projection,则默认返回所有键(默认省略)。
1
2
3
# projection入参格式为{columnA : 0/1,columnB : 0/1}
# columnA 和 columnB 表示你要查询的表中的字段,0/1 表示取或不取。
db.collection.find(query, projection)

如:{title:1},表示查询出的每条记录中只显示 title 字段内容。{description:0},表示查询出的每条记录中不显示 description 字段内容(其他字段都展示)。

注:_id(主键)字段默认为 1,可指定 {_id:0} 来不输出 _id 字段值。

指定 projection 格式如下,有两种模式

1
2
db.collection.find(query, {title: 1, by: 1}) // inclusion模式 指定返回的键,不返回其他键
db.collection.find(query, {title: 0, by: 0}) // exclusion模式 指定不返回的键,返回其他键

两种模式不可混用(因为这样的话无法推断其他键是否应返回)

1
2
3
4
db.collection.find(query, {title: 1, by: 0}) # 错误

# 只能全1或全0,除了在inclusion模式时可以指定_id为0
db.collection.find(query, {_id:0, title: 1, by: 1}) # 正确

若不想指定查询条件参数 query 可以 用 {} 代替,但是需要指定 projection 参数:

1
querydb.collection.find({}, {title: 1})

AND 条件

1
db.test1.find({key1:value1, key2:value2}).pretty()

OR 条件

使用关键字 $or,语法格式如下:

1
2
3
4
5
6
7
db.test1.find(
{
$or: [
{key1: value1}, {key2:value2}
]
}
).pretty()

AND 和 OR 联合使用

where likes>50 AND (by = 'test1' OR title = 'MongoDB')

1
db.test1.find({"likes": {$gt:50}, $or: [{"by": "test1"},{"title": "MongoDB"}]}).pretty()

模糊查询

1
2
3
4
5
6
7
8
# 查询 title 包含"教"字的文档:
db.col.find({title:/教/})

# 查询 title 字段以"教"字开头的文档:
db.col.find({title:/^教/})

# 查询 titl e字段以"教"字结尾的文档:
db.col.find({title:/教$/})

条件操作符

结合查询语句使用。

  • $gt — greater than >
  • $gte — gt equal >=
  • $lt — less than <
  • $lte — lt equal <=
  • $ne — not equal !=
  • $eq — equal =

$type 操作符

$type 操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。

获取 test1 集合中 title 为 String 的数据

1
2
3
db.test1.find({"title" : {$type : 2}})
# 或
db.test1.find({"title" : {$type : 'string'}})

Limit与Skip方法

1
2
3
4
5
6
7
8
9
# Limit() 方法
db.COLLECTION_NAME.find().limit(NUMBER)

# 如果你们没有指定limit()方法中的参数则显示集合中的所有数据。
db.test1.find({},{"title":1,_id:0}).limit(2)

# Skip() 方法
# skip()方法默认参数为 0
db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER)

skip 和limit方法只适合小数据量分页,如果是百万级效率就会非常低,因为skip方法是一条条数据数过去的,建议使用 where limit

排序

sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。

语法:

1
2
3
4
5
6
# 语法
db.COLLECTION_NAME.find().sort({KEY:1})

# 实例
# 按字段 likes 的降序排列
db.test1.find({},{"title":1,_id:0}).sort({"likes":-1})

索引

3.0.0 版本前创建索引方法为 db.collection.ensureIndex() ,之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。

语法

1
2
3
4
5
6
7
db.collection.createIndex(keys, options)

# 实例
db.test1.createIndex({"title":1})

# 可以设置使用多个字段创建索引(关系型数据库中称作复合索引)
db.test1.createIndex({"title":1,"description":-1})

语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引 -1 按降序来创建索引。

可选参数:

微信截图_20200924134809.png

1
2
3
4
5
6
7
8
9
10
11
# 查看集合索引
db.test1.getIndexes()

# 查看集合索引大小
db.test1.totalIndexSize()

# 删除集合所有索引
db.test1.dropIndexes()

# 删除集合指定索引
db.test1.dropIndex("索引名称")

TTL索引

保存最近三个月的文档(单位秒),当中途修改了createdAt的值时,
则不会删除文档(指定的时间是字段与当前时间的差值)。

db.user.createIndex({"createdAt": 1},{expireAfterSeconds: 60*60*24*3});

若需求变动,需要将三个月修改为一个月可以使用collMod,如下:

db.runCommand({collMod: 'user', index: {keyPattern:{"createdAt": 1}, expireAfterSeconds:60*60*24*1}});

TTL索引限制:

  • TTL索引是单字段索引,不能使用在聚合索引上
  • 不能在普通索引上再创建TTL索引,只能删除再建
  • _id主键上不能建立TTL索引
  • 一个集合上可以建立多个TTL索引
  • 索引不能包含多个字段
  • TTL索引可以用于普通索引一样进行排序和查询
  • 如果定义的字段不存在,则永不过期
  • 不能对capped集合创建TTL索引
  • TTL索引会每分钟检查超时文档,并进行删除操作。需要注意删除时候的并发问题(不要影响线上业务)

聚合

MongoDB中聚合( aggregate )主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)

aggregate() 方法

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

聚合的表达式:

微信截图_20200924142403.png

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
db.test1.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])
# 类似:
select by_user, count(*) from mycol group by by_user

# $project实例
# 只展示_id,tilte和author三个字段
db.article.aggregate(
{ $project : {
title : 1 ,
author : 1 ,
}}
);

# $match实例
db.article.aggregate(
{ $project : {
_id : 0 ,
title : 1 ,
author : 1
}});

# $match实例
db.articles.aggregate( [
{ $match : { score : { $gt : 70, $lte : 90 } } },
{ $group: { _id: null, count: { $sum: 1 } } }
] );

管道操作

MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match 使用 MongoDB 的标准查询操作。
  • $limit:用来限制 MongoDB 聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

注意:当 match 条件和 group 同时存在时,顺序会影响检索结果,必须先写 match 在前面。

时间关键字如下:

  • $dayOfYear: 返回该日期是这一年的第几天(全年 366 天)。
  • $dayOfMonth: 返回该日期是这一个月的第几天(1到31)。
  • $dayOfWeek: 返回的是这个周的星期几(1:星期日,7:星期六)。
  • $year: 返回该日期的年份部分。
  • $month: 返回该日期的月份部分( 1 到 12)。
  • $week: 返回该日期是所在年的第几个星期( 0 到 53)。
  • $hour: 返回该日期的小时部分。
  • $minute: 返回该日期的分钟部分。
  • $second: 返回该日期的秒部分(以0到59之间的数字形式返回日期的第二部分,但可以是60来计算闰秒)。
  • $millisecond:返回该日期的毫秒部分( 0 到 999)。
  • $dateToString: { $dateToString: { format: , date: } }。

实例:

1
2
3
4
5
6
7
8
9
10
db.getCollection('m_msg_tb').aggregate(
[
{$match:{m_id:10001,mark_time:{$gt:new Date(2017,8,0)}}},
{$group: {
_id: {$dayOfMonth:'$mark_time'},
pv: {$sum: 1}
}
},
{$sort: {"_id": 1}}
])

参考:

MongoDB 教程

utf8mb4 的最低mysql版本支持版本为5.5.3+,若不是,请升级到较新版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character_set_%' OR Variable_name LIKE 'collation%';
+--------------------------+----------------------------------+
| Variable_name | Value |
+--------------------------+----------------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/local/mysql/share/charsets/ |
| collation_connection | utf8_general_ci |
| collation_database | utf8_general_ci |
| collation_server | utf8_general_ci |
+--------------------------+----------------------------------+
11 rows in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
# 修改database默认的字符集
# 虽然修改了database的字符集为utf8mb4,但是实际只是修改了database新创建的表,默认使用utf8mb4,
# 原来已经存在的表,字符集并没有跟着改变,需要手动为每张表设置字符集
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci

# 修改表默认的字符集
ALTER TABLE table_name DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

# 修改表默认的字符集和所有字符列的字符集
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

修改MySQL配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# 对本地的mysql客户端的配置
[client]
default-character-set = utf8mb4

# 对其他远程连接的mysql客户端的配置
[mysql]
default-character-set = utf8mb4

# 本地mysql服务的配置
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 必须保证一下系统变量是 utf8mb4

# 客户端来源数据使用的字符集
character_set_client

# 连接层字符集
character_set_connection

# 当前选中数据库的默认字符集
character_set_database

# 查询结果字符集
character_set_results

# 默认的内部操作字符集
character_set_server

参考:

MySQL中的 utf8 并不是真正的UTF-8编码 ! !

mysql 修改字符集为utf8mb4

更改MySQL数据库的编码为utf8mb4

0%