MongoDB
<p>MongoDB 是一款开源的面向文档的数据库(document database), NoSQL 中一种,同样使用文档存储实现 NoSQL 的 DB 还有 MarkLogic、OrientDB、CouchDB 等等。</p><h2 id="安装">安装</h2>
<p>Mac 用户可以直接使用 Homebrew 安装,命令如下:</p>
<pre><code class="language-bash">sudo brew install mongodb
</code></pre>
<p>也可以自己到 MongoDB 的下载中心 下载对应的系统和版本,如果是 Linux 的话可以使用 <code>wget</code> 下载:</p>
<pre><code class="language-bash">wget "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.4.tgz"
</code></pre>
<p>并配置环境变量,如下:</p>
<pre><code class="language-bash">export PATH={MONGODB_DIR}/bin:$PATH
</code></pre>
<h2 id="启动">启动</h2>
<pre><code class="language-bash"># {mongo_db_file_path} 为指定的数据库文件存放位置,不支持~符号。如果使用默认位置 /data/db ,也需要先手动创建。
mongod --dbpath={mongo_db_file_path} --bind_ip_all --fork --logpath ./mongo.log
</code></pre>
<h2 id="终端连接">终端连接</h2>
<p>(1)本地连接</p>
<pre><code class="language-bash">mongo
</code></pre>
<p>(2)远程连接</p>
<pre><code class="language-bash"> mongo 172.2.0.3:27017
</code></pre>
<h2 id="基本概念">基本概念</h2>
<h3 id="bson">BSON</h3>
<p>MongoDB 的文件存储格式为 BSON,所谓 BSON,即是 Binary JSON,为 JSON 文档对象的二进制编码格式,其扩展了 JSON 的数据类型,支持浮点数和整数的区分,支持日期类型,支持直接存储 JS 的正则表达式,支持 32 位和 64 位数字的区分,支持直接存储 JS 函数等等。看起来 BSON 对于 JS 还是挺友好的呵。</p>
<blockquote>
<p>注意:从 bash 终端中输入的数值都会被存储为 64 位浮点类型。</p>
</blockquote>
<h3 id="文档document">文档(Document)</h3>
<p>一个文档就相当于关系型数据库中的<strong>行</strong>的概念,由多个键值有序组成,格式为 BSON。示例如下:</p>
<pre><code class="language-json">{ "_id" : ObjectId("5c05e74a65a27abc9a619f8a"), "a_key" : "a_value" }
</code></pre>
<p>其中 <code>_id</code> 是系统自动生成的键,当然也可以在创建时自定义值。</p>
<h3 id="集合collection">集合(Collection)</h3>
<p>一个集合就相当于关系型数据库中的<strong>表</strong>的概念,由多个文档组成。集合中的默认索引为 <code>_id</code> ,可以新建其他键的索引来优化查询,MongoDB 支持单字段索引(Single Field Index)、复合索引(Compound Index)以及多键索引(Multikey Index)等等,可以根据需求进行选用。</p>
<h3 id="数据库database">数据库(Database)</h3>
<p>多个集合组成一个数据库,不同的数据库之间文件是隔离的。单个 MongoDB 实例可以容纳多个独立数据库。默认系统存在以下的保留数据库:</p>
<ol>
<li>admin:用户权限相关</li>
<li>local:存储限于本地的集合</li>
<li>config:分片配置相关</li>
</ol>
<h2 id="索引原理">索引原理</h2>
<h3 id="普通索引原理">普通索引原理</h3>
<p>MongoDB 3.2 开始使用 WiredTiger 为默认的存储引擎(之前版本默认使用 MMAPv1 存储引擎),WiredTiger 引擎默认使用B-树为索引底层结构。</p>
<p>MongoDB 中的每个文档都有一个位置信息(pos),索引就是建立索引键到pos的映射。在 WiredTiger 中该映射使用 B-树结构存储。</p>
<p>为什么使用 B-树而不是 B+树呢?</p>
<p>查询快,B-树中的各个节点都包含了文档的位置信息,可以减少磁盘的随机IO次数。但区间查询比 B+树慢,MongoDB 的假设条件是用户对区间查询或者说遍历的需求远少于=单个查询。B+树的所有数据都存放在叶子结点,单个查询效率固定为 O(logn);而 B-树的文档索引存放在所有结点,单个查询效率最佳情况为 O(1)。</p>
<p>为什么使用 B-树而不是LSM树(日志结构合并树)呢?</p>
<p>LSM树的写入效率远高于读取效率,原因是牺牲了部分读取性能来使随机写变为顺序写。但在限制写入的场景下(比如限定每秒写入 30,000 条数据),LSM树的写入性能基本跟 B-树持平,但读取性能使 B-树的 1/4~1/2(见:https://xie.infoq.cn/article/4cba9eab7d6f90e9b548ab3d1 )。MongoDB 的假设条件是用户对数据的读取远多于写入。</p>
<h3 id="空间地理2d索引原理">空间地理2D索引原理</h3>
<p>MongoDB 的地理空间索引(geo2d index)很有趣。其索引值并不是存储位置点的 x、y 坐标,而是计算并存储其 geohash。geohash 是通过将平面不断切割成四等份(实现上采用平面四叉树表示),每一等份从坐标原点依次顺时针编码为 00、01、11、10,随着划分的不断精细,位置也越来越精确。</p>
<table>
<thead>
<tr>
<th>01</th>
<th>11</th>
</tr>
</thead>
<tbody>
<tr>
<td>00</td>
<td>10</td>
</tr>
</tbody>
</table>
<p>创建 2d 索引时通过 bits 位定义划分次数,命令如下:</p>
<pre><code class="language-bash">db.coll.createIndex({"lag":"2d"}, {"bits":int}))
</code></pre>
<p>bits 默认值为 26,也就是将地球表面进行 26 次四等份。将地球表面展开到一张矩形纸张上,可知纸张长为 <code>2*PI*R</code> (PI 为圆周率,R 为地球半径),第 1 次切割会在纸张长边分为 2 份,第 2 次则是 4 份,第 3 次则是 8 份,于是 26 次是 <code>2^26</code> 份,每份长度为:</p>
<pre><code>2*PI*R/(2^26) 或 2*PI*R/(1<<26)
</code></pre>
<p>计算可得 0.57 米,即 MongoDB 的 geo2d 索引精确度默认为 60 厘米。</p>
<h2 id="bash-操作">bash 操作</h2>
<h3 id="database-级别">database 级别</h3>
<pre><code class="language-bash"># 列出所有的数据库
> show dbs
# 查看当前使用的数据库
> db
# 切换当前使用的数据库
> use a_db
# 创建数据库
> use new_db
# 删除数据库
> db.dropDatabase()
</code></pre>
<h3 id="collection-级别">collection 级别</h3>
<pre><code class="language-bash"># 显示数据库中的所有 collection
> show collections
# 列出 collection 中的所有列
> db.a_collection.find()
# 删除 collection
> db.a_collection.drop()
# 新建 collection
> db.createCollection("new_collection")
# 重命名 collection
> db.a_collection.renameCollection("new_name")
# 清空 collection 中数据
> db.a_collection.drop({})
</code></pre>
<h3 id="docuement-级别">docuement 级别</h3>
<pre><code class="language-bash"># 插入文档
> db.a_collection.insert({
"a_key": "a_value",
"b_key": 100,
"c_key": true
})
# 列出所有文档,并美化
> db.a_collection.find().pretty()
# 查询记录条数
> db.a_collection.find().count()
# 略过前100条
> db.a_collection.find().skip(100)
# MongoDB AND 且过滤器
> db.a_collection.find({
"a_key": "a_value",
"c_key": true
})
# MongoDB OR 或过滤器
> db.a_collection.find({
$or:[
{ "a_key": "a_value"},
{ "a_key": "another_value" }
]
})
# MongoDB 投影,只返回指定的字段
> db.a_collection.find({},{"a_key", "c_key"})
# 查询存在某字段的文档
> db.a_collection.find({"a_key",{"$exists":true}})
# 更新单个文档
db.a_collection.update({"a_key": "a_value"},{$set:{"a_key": "another_value"}})
# 更新多个文档
db.a_collection.update({"a_key": "a_value"},{$set:{"a_key": "another_value"}},,{multi: true})
# 删除文档
db.a_collection.remove({"a_key": "a_value"})
# 后台执行创建单一复合索引操作
db.a_collection.createIndex({"a_key": 1,"c_key": -1},{unique: true,background: true})
# 查询所有索引
db.a_collection.getIndexes()
# 删除索引
db.a_collection.dropIndex({"a_key":1})
</code></pre>
<h2 id="java-操作">Java 操作</h2>
<h3 id="模块划分">模块划分</h3>
<ol>
<li>bson:高性能的编码解码。</li>
<li>mongodb-driver-core:核心库,抽取出来主要是用于自定义 API。</li>
<li>mongodb-driver-legacy:兼容旧的 API 的同步 Java Driver。</li>
<li>mongodb-driver-sync:只包含 MongoCollection 泛型接口,服从一套新的跨 Driver 的 CRUD 规范。</li>
<li>mongodb-driver:mongodb-driver-legacy + mongodb-driver-sync,新项目推荐使用它!</li>
<li>mongodb-driver-async:新的异步 API,充分利用 Netty 或者 Java7 的 AsynchronousSocketChannel 已达到快而非阻塞的 IO。</li>
<li>mongo-java-driver(uber-jar):包含 bson,mongodb-driver-core 和 mongodb-driver。</li>
</ol>
<h3 id="引入依赖">引入依赖</h3>
<pre><code class="language-groovy">dependencies {
compile 'org.mongodb:mongodb-driver-sync:3.9.1'
}
</code></pre>
<h3 id="在-v364-使用-mongouri">在 v3.6.4 使用 MongoURI</h3>
<pre><code class="language-java">String mongoUri = ConfigManager.getInstance().getString(DistributedConfig.MONGODB_URI);
ConnectionString connectionString = new ConnectionString(mongoUri);
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder()
.automatic(true)
.build()));
MongoClient mongoClient = new MongoClient(new MongoClientURI(mongoUri,
MongoClientOptions.builder()
.codecRegistry(
pojoCodecRegistry)));
String database = connectionString.getDatabase();
if (Strings.isNullOrEmpty(database)) {
database = "my_db";
}
MongoDatabase mongoDatabase = mongoClient.getDatabase(database);
</code></pre>
<h3 id="在--v391-使用-mongouri">在v3.9.1 使用 MongoURI</h3>
<pre><code class="language-java">public class Mongo {
private MongoDatabase mongoDatabase;
private Mongo() {
String mongoUri = ConfigManager.getInstance().getString(DistributedConfig.MONGODB_URI);
ConnectionString connectionString = new ConnectionString(mongoUri);
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder()
.automatic(true)
.build()));
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.codecRegistry(pojoCodecRegistry)
.build();
MongoClient mongoClient = MongoClients.create(settings);
String database = connectionString.getDatabase();
if (Strings.isNullOrEmpty(database)) {
database = "my_db";
}
MongoDatabase mongoDatabase = mongoClient.getDatabase(database);
}
public <T> MongoCollection<T> getCollection(Class<T> documentClass) {
return mongoDatabase.getCollection(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,
documentClass.getSimpleName()),
documentClass);
}
}
</code></pre>
<h3 id="事务支持">事务支持</h3>
<p>MongoDB 的事务支持始于 MongoDB 4.0,对应 Java Driver 版本为 3.8.0,对应 Python 版本为 3.7.0,详情阅读 Transactions and MongoDB Drivers - mongodb.com.</p>
<pre><code class="language-java">void runTransactionWithRetry(Runnable transactional) {
while (true) {
try {
transactional.run();
break;
} catch (MongoException e) {
System.out.println("Transaction aborted. Caught exception during transaction.");
if (e.hasErrorLabel(MongoException.TRANSIENT_TRANSACTION_ERROR_LABEL)) {
System.out.println("TransientTransactionError, aborting transaction and retrying ...");
continue;
} else {
throw e;
}
}
}
}
void commitWithRetry(ClientSession clientSession) {
while (true) {
try {
clientSession.commitTransaction();
System.out.println("Transaction committed");
break;
} catch (MongoException e) {
// can retry commit
if (e.hasErrorLabel(MongoException.UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)) {
System.out.println("UnknownTransactionCommitResult, retrying commit operation ...");
continue;
} else {
System.out.println("Exception during commit ...");
throw e;
}
}
}
}
void updateEmployeeInfo() {
MongoCollection<Document> employeesCollection = client.getDatabase("hr").getCollection("employees");
MongoCollection<Document> eventsCollection = client.getDatabase("hr").getCollection("events");
try (ClientSession clientSession = client.startSession()) {
clientSession.startTransaction();
employeesCollection.updateOne(clientSession,
Filters.eq("employee", 3),
Updates.set("status", "Inactive"));
eventsCollection.insertOne(clientSession,
new Document("employee", 3).append("status", new Document("new", "Inactive").append("old", "Active")));
commitWithRetry(clientSession);
}
}
void updateEmployeeInfoWithRetry() {
runTransactionWithRetry(this::updateEmployeeInfo);
}
</code></pre>
<h2 id="备份与还原">备份与还原</h2>
<p>使用 Mongo 安装包 bin 目录下的 mongodump 进行备份,mongorestore 进行还原。</p>
<h3 id="备份">备份</h3>
<pre><code class="language-bash">mongodump -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -o 文件存在路径
</code></pre>
<h3 id="还原">还原</h3>
<pre><code class="language-bash">mongorestore -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 --drop 文件存在路径
</code></pre>
<p>当为还原 bson 文件为</p>
<pre><code class="language-bash">mongorestore -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 --drop bson文件路径 -d 表名
</code></pre>
<h2 id="数据库迁移">数据库迁移</h2>
<pre><code class="language-bash">db.copyDatabase("db_to_rename","db_renamed","localhost")
</code></pre>
<h2 id="qa">Q&A</h2>
<h3 id="一个服务中该使用一个还是多个-mongoclient">一个服务中该使用一个还是多个 MongoClient?</h3>
<p>通常一个服务应使用一个全局的 MongoClient,并且 MongoClient 中已经实现了一个连接池,最大值默认为 1000000 的连接限制,这相当于没有限制。</p>
<p>参考:为什么 MongoDB 连接数被用满了? - mongoing.com</p>
<h3 id="invalid-bson-field-name-id">Invalid BSON field name id</h3>
<p>更新文档时出现该错误,原因是使用了 <code>updateOne</code> 但是没有 <code>$set</code> 字段,改为使用 <code>replaceOne</code> 就不用这么麻烦了。</p>
<h3 id="readstring-can-only-be-called-when-currentbsontype-is-string-not-when-currentbsontype-is-object_id">readString can only be called when CurrentBSONType is STRING, not when CurrentBSONType is OBJECT_ID</h3>
<p>给名为 <code>id</code> 的字段添加注解 <code>@BsonProperty("id")</code> 即可。</p>
<h3 id="mongowaitqueuefullexception">MongoWaitQueueFullException</h3>
<p>错误日志:</p>
<pre><code>com.mongodb.MongoWaitQueueFullException: Too many threads are already waiting for a connection. Max number of threads (maxWaitQueueSize) o
f 500 has been exceeded.
at com.mongodb.internal.connection.DefaultConnectionPool.createWaitQueueFullException(DefaultConnectionPool.java:280)
at com.mongodb.internal.connection.DefaultConnectionPool.get(DefaultConnectionPool.java:99)
at com.mongodb.internal.connection.DefaultConnectionPool.get(DefaultConnectionPool.java:92)
at com.mongodb.internal.connection.DefaultServer.getConnection(DefaultServer.java:85)
at com.mongodb.binding.ClusterBinding$ClusterBindingConnectionSource.getConnection(ClusterBinding.java:115)
at com.mongodb.operation.OperationHelper.withReleasableConnection(OperationHelper.java:424)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:192)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:67)
at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:193)
at com.mongodb.client.internal.MongoCollectionImpl.executeBulkWrite(MongoCollectionImpl.java:467)
at com.mongodb.client.internal.MongoCollectionImpl.bulkWrite(MongoCollectionImpl.java:447)
at com.mongodb.client.internal.MongoCollectionImpl.bulkWrite(MongoCollectionImpl.java:442)
</code></pre>
<h2 id="参考">参考</h2>
<ol>
<li>MongoDB Driver Quick Start - mongoDB</li>
<li>MongoDB学习(二):数据类型和基本概念 - Hejin.Wang</li>
<li>MongoDB索引原理 - mongoing.com</li>
</ol>
</div>
<div id="MySignature" role="contentinfo">
<br/>
本作品地址: https://www.cnblogs.com/lshare/p/11334467.html,作者: 東籬老農,採用知識共享署名 4.0 國際許可協議進行許可。
<br><br><br>
来源:https://www.cnblogs.com/lshare/p/11334467.html
頁:
[1]