MongoDB 是一款開源的面向文檔的數據庫(document database), NoSQL 中一種,同樣使用文檔存儲實現 NoSQL 的 DB 還有 MarkLogic、OrientDB、CouchDB 等等。
安裝
Mac 用戶可以直接使用 Homebrew 安裝,命令如下:
$ sudo brew install mongodb
也可以自己到 MongoDB 的下載中心 下載對應的系統和版本,如果是 Linux 的話可以使用 wget
下載:
wget "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.4.tgz"
並配置環境變量,如下:
$ export PATH={MONGODB_DIR}/bin:$PATH
啟動
# {mongo_db_file_path} 為指定的數據庫文件存放位置,不支持~符號。如果使用默認位置 /data/db ,也需要先手動創建。
$ mongod --dbpath={mongo_db_file_path} --bind_ip_all --fork --logpath ./mongo.log
終端連接
(1)本地連接
$ mongo
(2)遠程連接
$ mongo 172.2.0.3:27017
基本概念
BSON
MongoDB 的文件存儲格式為 BSON,所謂 BSON,即是 Binary JSON,為 JSON 文檔對象的二進制編碼格式,其擴展了 JSON 的數據類型,支持浮點數和整數的區分,支持日期類型,支持直接存儲 JS 的正則表達式,支持 32 位和 64 位數字的區分,支持直接存儲 JS 函數等等。看起來 BSON 對於 JS 還是挺友好的呵。
注意:從 Shell 終端中輸入的數值都會被存儲為 64 位浮點類型。
文檔(Document)
一個文檔就相當於關系型數據庫中的行的概念,由多個鍵值有序組成,格式為 BSON。示例如下:
{ "_id" : ObjectId("5c05e74a65a27abc9a619f8a"), "a_key" : "a_value" }
其中 _id
是系統自動生成的鍵,當然也可以在創建時自定義值。
集合(Collection)
一個集合就相當於關系型數據庫中的表的概念,由多個文檔組成。集合中的默認索引為 _id
,可以新建其他鍵的索引來優化查詢,MongoDB 支持單字段索引(Single Field Index)、復合索引(Compound Index)以及多鍵索引(Multikey Index)等等,可以根據需求進行選用。
數據庫(Database)
多個集合組成一個數據庫,不同的數據庫之間文件是隔離的。單個 MongoDB 實例可以容納多個獨立數據庫。默認系統存在以下的保留數據庫:
- admin:用戶權限相關
- local:存儲限於本地的集合
- config:分片配置相關
Shell 操作
database 級別
# 列出所有的數據庫
> show dbs
# 查看當前使用的數據庫
> db
# 切換當前使用的數據庫
> use a_db
# 創建數據庫
> use new_db
# 刪除數據庫
> db.dropDatabase()
collection 級別
# 顯示數據庫中的所有 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({})
docuement 級別
# 插入文檔
> 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})
Java 操作
模塊划分
- bson:高性能的編碼解碼。
- mongodb-driver-core:核心庫,抽取出來主要是用於自定義 API。
- mongodb-driver-legacy:兼容舊的 API 的同步 Java Driver。
- mongodb-driver-sync:只包含 MongoCollection 泛型接口,服從一套新的跨 Driver 的 CRUD 規范。
- mongodb-driver:mongodb-driver-legacy + mongodb-driver-sync,新項目推薦使用它!
- mongodb-driver-async:新的異步 API,充分利用 Netty 或者 Java7 的 AsynchronousSocketChannel 已達到快而非阻塞的 IO。
- mongo-java-driver(uber-jar):包含 bson, mongodb-driver-core 和 mongodb-driver。
引入依賴
dependencies {
compile 'org.mongodb:mongodb-driver-sync:3.9.1'
}
在 v3.6.4 使用 MongoURI
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);
在 v3.9.1 使用 MongoURI
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);
}
}
事務支持
MongoDB 的事務支持始於 MongoDB 4.0,對應 Java Driver 版本為 3.8.0,對應 Python 版本為 3.7.0,詳情閱讀 Transactions and MongoDB Drivers - mongodb.com.
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);
}
備份與還原
使用 Mongo 安裝包 bin 目錄下的 mongodump 進行備份,mongorestore 進行還原。
備份
mongodump -h IP --port 端口 -u 用戶名 -p 密碼 -d 數據庫 -o 文件存在路徑
還原
mongorestore -h IP --port 端口 -u 用戶名 -p 密碼 -d 數據庫 --drop 文件存在路徑
當為還原 bson 文件為
mongorestore -h IP --port 端口 -u 用戶名 -p 密碼 -d 數據庫 --drop bson文件路徑 -d 表名
數據庫遷移
db.copyDatabase("db_to_rename","db_renamed","localhost")
Q&A
一個服務中該使用一個還是多個 MongoClient?
通常一個服務應使用一個全局的 MongoClient,並且 MongoClient 中已經實現了一個連接池,最大值默認為 1000000 的連接限制,這相當於沒有限制。
參考:為什么 MongoDB 連接數被用滿了? - mongoing.com
Invalid BSON field name id
更新文檔時出現該錯誤,原因是使用了 updateOne
但是沒有 $set
字段,改為使用 replaceOne
就不用這么麻煩了。
readString can only be called when CurrentBSONType is STRING, not when CurrentBSONType is OBJECT_ID
給名為 id
的字段添加注解 @BsonProperty("id")
即可。
MongoWaitQueueFullException
錯誤日志:
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)