顯示具有 mongodb 標籤的文章。 顯示所有文章
顯示具有 mongodb 標籤的文章。 顯示所有文章

2020-12-07

定期備份 mongodb 並上傳到 s3

定期備份 mongodb 並上傳到 s3

使用 mongodump 滙出資料庫,並以 tar 壓縮,再以 s3cmd 上傳至 AWS S3 備份。

寫成 backup-mongo.sh,搭配 crontab 就可以定時備份了!以下中文的部份請自行換成真實資料:

#!/bin/bash # 設定當日日期字串為變數值 today=`date +"%Y-%m-%d"` # 將資料庫倒至暫存資料夾 /usr/bin/mongodump -h 芒果主機 -u 芒果用戶 -p 用戶密碼 --authenticationDatabase admin --db my_database --out "/home/ubuntu/mongo_data/bk-${today}" # 壓縮資料夾 /usr/bin/tar -zcvf "/home/ubuntu/mongo_data/my_database-${today}.tar.gz" -C "/home/ubuntu/mongo_data/bk-${today}" my_database # 使用 s3cmd 上傳壓縮檔到 s3 /usr/bin/s3cmd put "/home/ubuntu/mongo_data/my_database-${today}.tar.gz" s3://my_database/mongo-backup/ # 移除暫存資料夾 /usr/bin/rm -fr "/home/ubuntu/mongo_data/bk-${today}" # 移除暫存壓縮檔 /usr/bin/rm "/home/ubuntu/mongo_data/my_database-${today}.tar.gz"

要注意的是若使用 root 執行,在 /root 目錄下要有 s3cmd 的設定檔 .s3cfg。

2020-11-07

使用 mongoose 連線遠端 mongodb (ubuntu)

使用 mongoose 連線遠端 mongodb (ubuntu)

通常連線遠端的 Mongodb server,使用 connection string uri:

mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]

Admin 帳號設定方式同 這篇。但是在使用 mongoose 連線時,一般的 connection uri 設定一直無法正常連線。

爬了文才發現要在 mongoose 連線時,使用 auth, user, pass 三個另外的設定,uri 內就不用放帳號和密碼了。例如我的連線檔案 mongoose-connect.js:

const mongoose = require('mongoose'); mongoose.connect(process.env.MONGO_DSN, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, useFindAndModify: false, "auth": { "authSource": "admin" }, "user": process.env.MONGO_USER, "pass": process.env.MONGO_PASS, }); const db = mongoose.connection; db.on('error', (error)=>{ console.log('Mongoose connection error:', error); console.log(`mongoose.connection.readyState: ${mongoose.connection.readyState}`); }); db.on('open', ()=>{ console.log('Mongoose connected ~'); }); module.exports = mongoose;

2020-05-16

在 Raspbian 安裝 MongoDB 的慘痛經驗 (廢文)

在 Raspbian 安裝 MongoDB 的慘痛經驗 (廢文)

由於 Raspberry Pi 4 model B 的 CPU ARMv7l 為 32 bit,所以只能安裝舊版的 MongoDB。若是網路架構,可以將 MongoDB 裝在別的主機或雲端,使用較新版本的 MongoDB。

在 Raspbian 中查看提供的 MongoDB 版本就好,不要安裝:

pi@raspberrypi:~ $ apt-cache policy mongodb mongodb: 已安裝:(無) 候選: 1:2.4.14-4 版本列表: 1:2.4.14-4 500 500 http://raspbian.raspberrypi.org/raspbian buster/main armhf Packages

只提供 2.4 的版本真的太老舊了,官方 drivers 都不支援了,真的別裝!

如果使用 MongoDB 3.2 以上是必要的那就請用 Ubuntu 20.04 LTS (64-bit),目前已經可以透過 Imager 安裝這個版本了。

以下是做個記錄,不要實作,在 Raspbian,MongoDB 3.2 和 3.0 32-bit 都無法執行,殘念。

支援 32-bit MongoDB 的最高版本 3.2.22,請參考這篇

可以透過瀏覽器到 MongoDB 官網下載中心 選取「Version 3.2.22」>「Linux 32-bit lagecy」>「TGZ」下載(拷備表單下的連結也可以)。

wget https://fastdl.mongodb.org/linux/mongodb-linux-i686-3.2.22.tgz # 下載 tar -zxvf mongodb-linux-i686-3.2.22.tgz # 解壓縮 sudo cp ./mongodb-linux-i686-3.2.22/bin/* /usr/bin/ # 複製檔案

設定啟動檔

wget https://raw.githubusercontent.com/mongodb/mongo/master/debian/init.d # 下載啟動檔 sudo mv init.d /etc/init.d/mongod # 移動檔案 sudo chmod 755 /etc/init.d/mongod # 變更屬性

建立設定檔 /etc/mongod.conf

storage: dbPath: /var/lib/mongo journal: enabled: true engine: mmapv1 systemLog: destination: file logAppend: true path: /var/log/mongodb/mongod.log processManagement: fork: true net: port: 27017 bindIp: 0.0.0.0

建立 mongodb 用戶

sudo useradd --home-dir /var/lib/mongo --shell /bin/false mongodb sudo passwd mongodb

建立存放資料的資料夾 /var/lib/mongo

# 存放資料庫資料 sudo mkdir /var/lib/mongo sudo chown -R mongodb /var/lib/mongo sudo chgrp -R mongodb /var/lib/mongo # 記錄檔 sudo mkdir /var/log/mongodb sudo chown -R mongodb /var/log/mongodb sudo chgrp -R mongodb /var/log/mongodb # pid sudo touch /var/run/mongod.pid sudo chown mongodb /var/run/mongod.pid sudo chgrp mongodb /var/run/mongod.pid

初始化服務:

sudo update-rc.d mongod defaults

最後重開機,然後還是不能執行。

2020-05-10

以 Flask / MongoDB 製作 通訊錄 CRUD

以 Flask / MongoDB 製作 通訊錄 CRUD

程式碼太多,請參考本文的專案 https://github.com/shinder/flask-practice,以下只列出部份程式碼:

主程式:

import modules.address_book app.add_url_rule('/address-book/list/', None, modules.address_book.ab_list) app.add_url_rule('/address-book/list/<int:page>', None, modules.address_book.ab_list) app.add_url_rule('/address-book/edit/<_id>', None, modules.address_book.ab_edit_get, methods=['GET']) app.add_url_rule('/address-book/edit', None, modules.address_book.ab_edit_post, methods=['POST']) app.add_url_rule('/address-book/add', None, modules.address_book.ab_add_get, methods=['GET']) app.add_url_rule('/address-book/add', None, modules.address_book.ab_add_post, methods=['POST']) app.add_url_rule('/address-book/delete/<_id>', None, modules.address_book.ab_delete)

app/modules/address_book.py 的內容:

from flask import Flask, request, render_template, session, jsonify, redirect from bson.objectid import ObjectId import json import math import re import modules.mongo_connection email_pattern = r"^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$" def ab_list(page=1): (db, connection) = modules.mongo_connection.getDB('test') output = { 'page_name': 'ab-list', 'page_title': '列表 - 通訊錄', 'totalRows': 0, # 總筆數 'perPage': 3, # 每一頁最多幾筆 'totalPages': 0, # 總頁數 'page': page, # 用戶要查看的頁數 'rows': [], # 當頁的資料 } output['totalRows'] = db.address_book.count_documents({}) output['totalPages'] = math.ceil(output['totalRows']/output['perPage']) output['page'] = 1 if page < 1 else page if output['page'] > output['totalPages']: output['page'] = output['totalPages'] if output['totalRows']==0: output['rows'] = [] else: cursor = db.address_book.find({}, sort=[('_id', -1)], skip=(output['page']-1) * output['perPage'], limit=output['perPage'] ) for doc in cursor: doc['_id'] = str(doc['_id']) output['rows'].append(doc) return render_template('address-book/list.html', **output) def ab_edit_get(_id): (db, connection) = modules.mongo_connection.getDB('test') try: oid = ObjectId(_id) except: return redirect("/address-book/list/1", code=302) row = db.address_book.find_one({'_id': oid}) row['_id'] = _id # 使用字串 if not row: return redirect("/address-book/list/1", code=302) else: row['page_name'] = 'ab_edit' row['page_title'] = '修改 - 通訊錄' return render_template('address-book/edit.html', **row) def ab_edit_post(): output = { 'success': False, 'error': '', } if len(request.form.get('name')) < 2: output['error'] = '姓名字元長度太短' return output email_match = re.search(email_pattern, request.form.get('email'), re.I) if not email_match: output['error'] = 'Email 格式錯誤' return output (db, connection) = modules.mongo_connection.getDB('test') doc = request.form.to_dict() _id = doc['_id'] del doc['_id'] rr = db.address_book.replace_one({'_id': ObjectId(_id)}, doc) # print(rr) # pymongo.results.UpdateResult if rr.modified_count==1: output['success'] = True else: output['error'] = '資料沒有變更'; return output def ab_add_get(): output = { 'page_name': 'ab_add', 'page_title': '新增 - 通訊錄', } return render_template('address-book/add.html', **output) def ab_add_post(): output = { 'success': False, 'error': '', } if len(request.form.get('name')) < 2: output['error'] = '姓名字元長度太短' return output email_match = re.search(email_pattern, request.form.get('email'), re.I) if not email_match: output['error'] = 'Email 格式錯誤' return output (db, connection) = modules.mongo_connection.getDB('test') doc = request.form.to_dict() rr = db.address_book.insert_one(doc) # print(dir(rr)) # InsertOneResult if rr.inserted_id: output['success'] = True else: output['error'] = '資料沒有新增'; return output def ab_delete(_id): (db, connection) = modules.mongo_connection.getDB('test') try: oid = ObjectId(_id) except: return redirect("/address-book/list/1", code=302) rr = db.address_book.delete_one({'_id': oid}) # print(dir(rr)) # DeleteResult referer = request.headers.get('referer') if not referer: return redirect("/address-book/list/1", code=302) else: return redirect(referer, code=302)

2020-05-09

Flask 連線 MongoDB

Flask 連線 MongoDB

本文的參考專案 https://github.com/shinder/flask-practice

這裡直接用最基本的 PyMongo 連線方式,官方文件

建立連線模組,app/modules/mongo_connection.py:

from pymongo import MongoClient url = 'mongodb://localhost:27017' client = None def getDB(db_name): global client if not client: client = MongoClient(url) return (client[db_name], client)

這個 MongoDB 連線模組的想法和 mysql_connection.py 的想法相同,有使用才會連線。測試的 route 部份:

import modules.mongo_connection @app.route('/try-mongo') def try_mongo(): (db, c) = modules.mongo_connection.getDB('test') one = db.inventory.find_one() one['_id'] = str(one['_id']) # 將 ObjectId 轉換為字串顯示 return one

2020-05-02

NodeJS 連線 MongoDB

NodeJS 連線 MongoDB

假設我們有個現成的 node/express 專案,首先安裝 mongodb 套件:

npm i mongodb

官方 mongodb 套件說明 裡面有各版本的教學和 API 文件。 先撰寫可以建立連線並使用某個 DB 的模組,在此檔名為 mdb_connect.js,只要滙出 getDB 方法即可:

const MongoClient = require('mongodb').MongoClient; const url = 'mongodb://localhost:27017'; const dbName = 'test'; let _db; // 存放對應 DB 的物件 const client = new MongoClient(url, {useUnifiedTopology: true}); client.connect() .then(c => { _db = client.db(dbName); // c 同 client }) .catch(error=>{ console.log('Cannot connect the mongodb server!'); console.log(error); }); const getDB = ()=>{ if(!_db) throw new Error('No MongoDB connection!'); return _db; }; module.exports = getDB;

模組被滙入之後,就開始依設定連線,並將 Db 物件存放到 _db 變數內,呼叫 getDB() 即回傳 _db 所指的 Db 物件。 以下為在 express app 下使用的情況:

const getDB = require(__dirname + '/mdb_connect'); app.get('/try-mdb', (req, res)=>{ const mdb = getDB(); mdb.collection('books') .find({}) .toArray() // Cursor 的 toArray() .then((ar)=>{ res.json(ar); }) });

之前 關聯查詢 的用法,另外用了 AggregationCursor 的 forEach() 方法:

app.get('/try-mdb2', (req, res)=>{ const mdb = getDB(); const ar = []; mdb.collection('books') .aggregate([ { $lookup: { from: 'publishers', foreignField: '_id', localField: 'publisher_id', as: 'publisher' } } ]) .forEach(function(doc){ ar.push(doc) }).then(()=>{ res.json(ar); }) });

2020-05-01

MongoDB Collections 之間的關聯性

MongoDB Collections 之間的關聯性

MongoDB 官方關於關聯性的說明,在關聯式資料庫中,表和表之間的關聯是很平常的事情,在 MongoDB 為了方便資料的維護,當然也有易於處理關聯的設計。在此就不談一對一的情況。

官方 以文件參照建構一對多的模型 裡面的例子其實很容易了解,我們就以裡面的範例來討論。

以下是直接使用嵌入子文件的方式來處理 books 中的出版商資料,很明顯的在維護上每次要更動的資料量會很多:

{ title: "MongoDB: The Definitive Guide", author: ["Kristina Chodorow", "Mike Dirolf"], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher: { name: "O'Reilly Media", founded: 1980, location: "CA" } } { title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher: { name: "O'Reilly Media", founded: 1980, location: "CA" } }

另一種是將出版商出版的書籍記錄在出版商的 collection publishers 內。缺點是,若要從書籍去找出版商,這樣子的效能會比較差:

{ name: "O'Reilly Media", founded: 1980, location: "CA", books: [123456789, 234567890, ...] } { _id: 123456789, title: "MongoDB: The Definitive Guide", author: ["Kristina Chodorow", "Mike Dirolf"], published_date: ISODate("2010-09-24"), pages: 216, language: "English" } { _id: 234567890, title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English" }

比較好的做法,就是 books 的 publisher_id 關聯到 publishers 的 _id。

db.publishers.insertOne({ _id: "oreilly", name: "O'Reilly Media", founded: 1980, location: "CA" }) db.books.insertMany([{ _id: 123456789, title: "MongoDB: The Definitive Guide", author: ["Kristina Chodorow", "Mike Dirolf"], published_date: ISODate("2010-09-24"), pages: 216, language: "English", publisher_id: "oreilly" }, { _id: 234567890, title: "50 Tips and Tricks for MongoDB Developer", author: "Kristina Chodorow", published_date: ISODate("2011-05-06"), pages: 68, language: "English", publisher_id: "oreilly" }])

查詢時,使用 aggregate() 和 $lookup$lookup 可以提供同關聯資料庫的 JOIN,方式如下:

db.books.aggregate([ { $lookup: { from: 'publishers', foreignField: '_id', localField: 'publisher_id', as: 'publisher' } } ]).pretty()
  • from: 為要合併查詢的 collection (publishers)。
  • foreignField: 為 publishers 的對應欄位。
  • localField: 為 books 中對應 publishers._id 的欄位。
  • as: 為查詢結果將 publishers 的文件所放入的欄位(暫時,查詢呈現)。

MongoDB 資料格式驗證 (json schema validation)

MongoDB 資料格式驗證 (json schema validation)

MongoDB 雖然在儲存的資料上很有彈性,但在很多時候太彈性的資料反而不好處理。所以在 collections 上也提供了格式驗證的功能,當設定了 $jsonSchema 表示新增的資料或修改後的資料都必須符合設定的格式。

官方的格式驗證說明 的例子來說明,可以在建立 collection 時,設定格式驗證規則:

db.createCollection("students", { validator: { $jsonSchema: { bsonType: "object", // 文件的類型 required: [ "name", "year", "major", "address" ], // 必要欄位 properties: { name: { bsonType: "string", // 欄位類型:字串 description: "說明的文字" }, year: { bsonType: "int", minimum: 2017, // 最小值 maximum: 3017 // 最大值 }, major: { enum: [ "Math", "English", "Computer Science", "History", null ], description: "必須是上列的其中一個" }, gpa: { bsonType: "double", description: "must be a double if the field exists" }, address: { bsonType: "object", required: [ "city" ], // 必要的子欄位 properties: { street: { bsonType: "string", description: "must be a string if the field exists" }, city: { bsonType: "string", "description": "must be a string and is required" } } } } } } })

BSON Types 的官方參考

也可以使用 collMod 指令,變更 collection 的格式驗證規則:

db.runCommand({ collMod: "contacts", validator: { $jsonSchema: { bsonType: "object", required: ["phone"], properties: { phone: { bsonType: "string", description: "must be a string and is required" }, email: { bsonType: "string", pattern: "@mongodb\.com$", description: "must be a string and match the regular expression pattern" }, status: { enum: ["Unknown", "Incomplete"], description: "can only be one of the enum values" } } } }, validationLevel: "moderate", validationAction: "warn" })

使用 db.getCollectionInfos() 可以顯示各個 collections 的詳細訊息。

> db.runCommand({ collMod: "compoundTest", validator: { $jsonSchema: { bsonType: "object", required: ['a', 'b'], properties: { a: { bsonType: "int" }, b: { bsonType: "int" } } } }, validationLevel: "moderate", // 等級 validationAction: "warn" // 沒通過時 }) > db.getCollectionInfos({name: 'compoundTest'})

2020-04-30

MongoDB 索引

MongoDB 索引

MongoDB 和傳統關聯式資料庫概念相念,都離不開 CRUD,裡面也是讀取(查詢)資料是最多變化的,也可能是最複雜的。其他三個動作,只要讀取熟悉了之後,就相對簡單。

讀取資料,經常需要排序,為了效能也有索引的設置,其中包含單欄(single field)索引和複合索引(compound index)。

首先先使用 explain() 查看讀取的策略,如下列(省略了不相干的內容)。在 queryPlanner.winningPlan 可以看到 stage 值為 COLLSCAN,代表 collections scan,也就是逐筆讀取。

> db.inventory.find() { "_id" : ObjectId("5ea4715cda0c749138d46e52"), "item" : "paper", "qty" : 100 } { "_id" : ObjectId("5ea4715cda0c749138d46e53"), "item" : "journal", "quantity" : 25 } { "_id" : ObjectId("5ea4715cda0c749138d46e54"), "item" : "planner", "qty" : 75 } { "_id" : ObjectId("5ea4715cda0c749138d46e55"), "item" : "postcard", "qty" : 45 } > db.inventory.find({item: 'postcard'}).explain() { "queryPlanner" : { "winningPlan" : { "stage" : "COLLSCAN", "filter" : { "item" : { "$eq" : "postcard" } }, "direction" : "forward" } } }

接著我們使用 createIndex() 新增一個 item 欄位的升冪索引。numIndexesAfter 表示加入索引後的索引數量。再使用 explain() 查看讀取的策略,stage 變成 FETCH。原則上使用 item 這欄為條件讀取資料時,速度會加快許多。

> db.inventory.createIndex({item: 1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } > db.inventory.getIndexes() // 查看所有索引 [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.inventory" }, { "v" : 2, "key" : { "item" : 1 }, "name" : "item_1", "ns" : "test.inventory" } ] > db.inventory.find({item: 'postcard'}).explain() { "queryPlanner" : { "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "item" : 1 }, "indexName" : "item_1", "isMultiKey" : false, "multiKeyPaths" : { "item" : [ ] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "item" : [ "[\"postcard\", \"postcard\"]" ] } } } } }

組合索引的官方說明 先新增測試的資料,並建立複合索引,使用 {a: 1, b: 1}

> db.compoundTest.insertMany([ ... {a: 10, b: 2}, {a: 10, b: 8}, {a: 10, b: 6}, ... {a: 70, b: 2}, {a: 70, b: 8}, {a: 70, b: 6}, ... {a: 30, b: 2}, {a: 30, b: 8}, {a: 30, b: 6}, ... ]) > db.compoundTest.createIndex({a:1, b:1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } > db.compoundTest.getIndexes() [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.compoundTest" }, { "v" : 2, "key" : { "a" : 1, "b" : 1 }, "name" : "a_1_b_1", "ns" : "test.compoundTest" } ]

以下是測試及取得 queryPlanner.winningPlan.stage 的結果:

db.compoundTest.explain().aggregate({$sort:{a: 1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: -1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: 1, b:1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: 1, b:-1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{a: -1, b:-1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: -1, b:1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{b: 1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{b: -1}}) // COLLSCAN

由結果可以知道,複合索引的欄位是有優先順序的。我們 a 放在前面,b 放在後面,所以用 a 去排序的時候都是有效的。a 加上 b 時,只有 {a: 1, b:1}{a: -1, b:-1} 是有效的,因為 {a: -1, b:-1} 是設定的相反順序排序。 所有 b 為主要順序欄位的,索引都無法發揮省時的效果,只能逐筆排查詢。

2020-04-29

MongoDB 移除資料

MongoDB 移除資料

承上篇 MongoDB 更新資料,延用相同的 db 和 collections。

官方的文件說明

使用 mongo shell 操作,刪除資料使用 deleteOne() 或 deleteMany():

> db.inventory.find() { "_id" : ObjectId("5ea4715cda0c749138d46e52"), "item" : "paper", "qty" : 100 } { "_id" : ObjectId("5ea4715cda0c749138d46e53"), "item" : "journal", "quantity" : 25 } { "_id" : ObjectId("5ea4715cda0c749138d46e54"), "item" : "planner", "qty" : 75 } { "_id" : ObjectId("5ea4715cda0c749138d46e55"), "item" : "postcard", "qty" : 45 } { "_id" : ObjectId("5ea6f2ab40e9db6af06a231a"), "item" : "notebook1", "qty" : 50 } { "_id" : ObjectId("5ea6f53240e9db6af06a231b"), "item" : "notebook2", "quantity" : 30 } { "_id" : ObjectId("5ea6f53240e9db6af06a231c"), "item" : "notebook3", "quantity" : 13 } > db.inventory.deleteOne({item:/note/}) { "acknowledged" : true, "deletedCount" : 1 } > db.inventory.deleteMany({item:/note/}) { "acknowledged" : true, "deletedCount" : 2 }

MongoDB 更新資料

MongoDB 更新資料

承上篇 MongoDB 新增資料,延用相同的 db 和 collections。

官方的文件說明

使用 mongo shell 操作,更新單筆資料使用 updateOne():

> db.inventory.updateOne( ... { ... item: 'notebook2' ... }, { ... $set: {qty: 30}, ... $currentDate: { lastModified: true } ... }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

第一個參數為條件限定,可以使用 $lt 等運算子。 第二個參數的 $set 為所要變更的設定值;$currentDate 為要設定為當下時間的屬性。

更新多筆使用 updateMany():

> db.inventory.updateMany( ... { qty: { $lte: 30 } }, ... { ... $set: { 'size.uom': "in" }, ... $rename: { qty: 'quantity' }, ... $unset: { status: '' }, ... $currentDate: { lastModified: true } ... } ... ) { "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }

$rename 為變更欄位( 屬性)名稱;$unset 刪除欄位。

replaceOne() 用來取代某個 document:

> db.inventory.replaceOne( ... { item: /notebook/ }, ... { 'hello': 'my-test' } ... ) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

replaceOne() 的第一個參數通常會使用 _id 或不會重複的欄位來篩選資料。

2020-04-28

設定 MongoDB Atlas

設定 MongoDB Atlas

Atlas 是 MongoDB 的資料庫雲端服務,這個服務建立在 AWS、GCP 和 Azure 三大雲端服務之上,提供了許多線上功能和工具。

對一般用戶來說,比較吸引人的是可以在註冊帳號後,免費使用一個基本款固定容量的雲端資料庫(號稱永久免費)。當然如果有更多容量和效能的需求,可以使用付費服務。

Atlas 可以使用自家的 MongoDB Compass 或 mongo shell 操作和管理,當然也可以使用 Robo 3T 等第三方工具管理。

以下是設定 Atlas 的過程簡述:

  • 到 MongoDB 官網註冊一個帳號,或按「try free」註冊。

  • 登入後,點按「Build a Cluster」按鈕,以建立 Altas Cluster。供應商不知道選誰就使用預設 AWS,服務中心(地區)也是一樣不知道就選預設值。然後給個 Cluster Name,使用變數命名規則即可,我是命名為 ClusterShin。

  • 點選左側的 SECURITY > Database Access,新增可存取資料庫的用戶。

  • 點選左側的 SECURITY > Network Access,新增可存取資料庫的 IP。測試的話,可以先選「ALLOW ACCESS FROM ANYWHERE」。若是 production 環境建議使用固定 IP。

  • 點選左側的 DATA STORAGE > Clusters,然後在 Clusters 畫面中,找到你的 Cluster,點選「conect」按鈕。

  • 在跳出的 lightbox 中,點選「Connect with the mongo shell」,點選「I have the mongo shell installed」,拷備連結的字串,如下。

mongo "mongodb+srv://clustershin-g2os9.mongodb.net/test" --username shinderlin

把拷備的字串貼到 terminal 執行,接著輸入密碼即可操作。

2020-04-27

MongoDB 新增資料

MongoDB 新增資料

同樣使用 mongo shell 操作,新增單筆使用 insertOne():

> use test switched to db test > db.inventory.insertOne({ ... "item": "notebook1", ... "qty": 50, ... "size": { ... "h": 8.5, ... "w": 11, ... "uom": "in" ... }, ... "status": "A" ... }) { "acknowledged" : true, "insertedId" : ObjectId("5ea6f2ab40e9db6af06a231a") }

新增多筆使用 insertMany():

> db.inventory.insertMany([ ... { ... "item": "notebook2", ... "qty": 10 ... }, ... { ... "item": "notebook3", ... "qty": 13 ... } ... ]) { "acknowledged" : true, "insertedIds" : [ ObjectId("5ea6f53240e9db6af06a231b"), ObjectId("5ea6f53240e9db6af06a231c") ] }

尋找剛剛新增的兩筆:

> db.inventory.find({item:/^notebook[23]/}).pretty() { "_id" : ObjectId("5ea6f53240e9db6af06a231b"), "item" : "notebook2", "qty" : 10 } { "_id" : ObjectId("5ea6f53240e9db6af06a231c"), "item" : "notebook3", "qty" : 13 }

2020-04-26

使用 MongoDB 的 find() 及 aggregate()

使用 MongoDB 的 find() 及 aggregate()

db.collection.find() 官方的說明文件在 https://docs.mongodb.com/manual/reference/method/db.collection.find/

先滙入 https://github.com/ozlerhakan/mongodb-json-files 裡 datasets 的 books.json 到 test 資料庫:

$ mongoimport --db test --collection books --file ~/downloads/books.json 2020-04-26T08:21:27.742+0800 connected to: mongodb://localhost/ 2020-04-26T08:21:29.561+0800 431 document(s) imported successfully. 0 document(s) failed to import.

先來看篩選(filter),以下是使用 mongo shell 的操作:

> use test > db.books.findOne() # 先找出第一筆查看大概的結構 { "_id" : 3, "title" : "Specification by Example", "isbn" : "1617290084", "pageCount" : 0, "publishedDate" : ISODate("2011-06-03T07:00:00Z"), "thumbnailUrl" : "https://s3.amazonaws.com/AKIAJC5RLADLUMVRPFDQ.book-thumb-images/adzic.jpg", "status" : "PUBLISH", "authors" : [ "Gojko Adzic" ], "categories" : [ "Software Engineering" ] } > db.books.find({isbn: 1617290084}) #找不到資料,型別不對 > db.books.find({isbn: '1617290084'}) #找到第一筆 # categoriries 為陣列,使用字串篩選時,結果會是陣列中有包含該字串的資料都會找出來 > db.books.find({categories: 'Software Engineering'}) # 計算個數 > db.books.find({categories: 'Software Engineering'}).count() # 只查看前兩筆 > db.books.find({categories: 'Software Engineering'}).limit(2) # 跳過 3 筆,取 5 筆 > db.books.find({categories: 'Software Engineering'}).skip(3).limit(5) # categoriries 為陣列,使用陣列篩選時,值必須相同(序順也必須一樣) > db.books.find({categories: ['Software Engineering']}) # 以下搜尋的結果會不同 > db.books.find({categories: ['Java', 'Software Engineering']}) > db.books.find({categories: ['Software Engineering', 'Java']}) # 陣列中特定位置的值 > db.books.find({'categories.1': 'Java'}) > db.books.find({'categories.1': 'Software Engineering'}) # 若是物件也是類似的作法 {'name.first': 'Bill'} # $all 同時包含(AND運算,不管順序) > db.books.find({categories: { $all: ['Software Engineering', 'Java']}}) # $in 包含任一個(OR運算) > db.books.find({categories: { $in: ['Software Engineering', 'XML']}}) # $nin 不包含任一個 > db.books.find({categories: {$nin: ['Java']}}).count() # 包含 'Software Engineering',但不包含 'Java' > db.books.find({categories: { $in: ['Software Engineering'], $nin: ['Java']}}) # 使用 Regular Expression > db.books.find({categories: /Software/}) # 陣列中任一符合 > db.books.find({title: /Practice/}) # 字串 # 沒有某個欄位 > db.books.find({publishedDate: {$exists: false} }).count() # 有某個欄位 > db.books.find({publishedDate: {$exists: true} }).count() # 某個物件的值大於、等於、小於、不等於、大於等於、小於等於、範圍 > db.inventory.find({'size.h': {$gt: 10}}) > db.inventory.find({'size.h': {$eq: 10}}) > db.inventory.find({'size.h': {$lt: 10}}) > db.inventory.find({'size.h': {$ne: 10}}) > db.inventory.find({'size.h': {$gte: 10}}) > db.inventory.find({'size.h': {$lte: 10}}) > db.inventory.find({'size.h': {$gt: 9, $lt: 15}}) # 日期 > db.books.find({publishedDate: {$gte: new Date('2014-01-01')} })

投射(project),就是要看哪些欄位,要看給 1,不看給 0。 可以使用 find() 的第二個參數,或使用 aggregate()

# 只顯示 title, _id 預設是顯示的 # 使用 find() 的第二個參數 > db.books.find({}, {title:1, _id:0}) > db.books.find({publishedDate: {$gte: new Date('2014-01-01')} }, {title:1, publishedDate:1}) # 使用 aggregate() > db.books.aggregate({$project: {title:1, _id:0}}) > db.books.aggregate([{ $match: { publishedDate: { $gte: new Date('2014-01-01') } } },{ $project: {title:1, publishedDate:1} }]) # 不顯示設定的欄位,使用 find() 的第二個參數 > db.books.find({}, {shortDescription: 0, longDescription: 0}).limit(5) # 不顯示設定的欄位,使用 aggregate() > db.books.aggregate([{$project: {shortDescription: 0, longDescription: 0}}, {$limit: 5}])

排序(sort),依欄位,升冪給 1,降冪給 -1。

> db.books.aggregate([{ $match: { categories: 'XML' } }, { $project: {title:1, publishedDate:1} }, { $sort: {publishedDate: -1} }])

複雜的操作都落在 aggregate() 上。

使用 mongo shell 建立資料庫

使用 mongo shell 建立資料庫

在 Mac 安裝好 MongoDB 後,在 terminal 輸入 mongo 即可進入 mongo shell,以下是建立資料庫和 collections 的方式:

> show databases # 顯示所有資料庫列表 admin 0.000GB config 0.000GB local 0.000GB

Mongo shell 沒有 create database 之類的指令,而是直接使用 use,不論該資料庫是否存在。使用新的 collection 也是同樣的情況。

> use test #切換到現有的資料庫或新資料庫 switched to db test > db #查看目前操作的資料庫 test > db.testCol.insertOne({a:1}) #直接在新的 collection 新增資料 { "acknowledged" : true, "insertedId" : ObjectId("5ea46d5864bc492ceab2d55e") } > show collections #顯示資料庫內的 collections testCol > db.dropDatabase() #刪除資料庫,沒事別亂用 { "dropped" : "test", "ok" : 1 } >

MongoDB 的核心是 JavaScript 引擎,mongo shell 在很多地方用起來也像是在寫 JS 一樣。

另外,官方有使用 mongoimport 滙入資料的說明在 https://docs.mongodb.com/guides/server/import/。 以下是沒使用帳密的滙入方式:

$ mongoimport --db test --collection inventory --file ~/downloads/inventory.crud.json 2020-04-26T01:20:28.490+0800 connected to: mongodb://localhost/ 2020-04-26T01:20:28.981+0800 5 document(s) imported successfully. 0 document(s) failed to import. $

滙出資料的說明在 https://docs.mongodb.com/manual/reference/program/mongoexport/

$ mongoexport --collection=inventory --db=test --out=/Users/shinder/downloads/inventory.json 2020-04-26T01:30:09.932+0800 connected to: mongodb://localhost/ 2020-04-26T01:30:10.641+0800 exported 5 records $

2020-04-25

在 Mac 上安裝 MongoDB community

在 Mac 上安裝 MongoDB community

官方文件說明 其實很清楚了,但還是筆記一下。

首先 Mac 的環境要有 homebrew。

追踪 mongodb 官方的 brew 版本資訊:

brew tap mongodb/brew

安裝 mongodb-community,目前的穩定版本為 4.2

brew install mongodb-community

安裝後的設定檔及資料檔案位置:

  • 設定檔位置: /usr/local/etc/mongod.conf
  • 日誌檔位置: /usr/local/var/log/mongodb
  • 資料檔位置: /usr/local/var/mongodb

安裝好就可以啟動服務了:

brew services start mongodb-community

停止服務:

brew services stop mongodb-community

2019-02-09

MongoDB 遠端連線 (ubuntu 18.04)

MongoDB 的安裝可以直接參考官方說明:
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

這裡的 MongoDB 版本為 4.0.6。MongoDB 安裝完成後,只允許 localhost 的連線,而且沒有任何的權限限制,可以連上線的都是管理者權限。但不用太擔心,因為沒有設定的情況下,遠端是無法連線的。

如果使用 ssh 連線到主機,再使用 mongo client 管理,理應也不需要設定。但我希望可以用家裡的 mac 使用 Robo 3T 連線到主機管理,這個時候就需要做些設定,本篇是採用簡便的 Role-Based Access Control 做法。

1. 針對 admin 資料庫設定管理者。選用 admin 資料庫,並建立管理權限的管理者帳號,請參考 Enable Auth 。預設的權限角色請參考 Built-In Roles 。
use admin
db.createUser(
  {
    user: "myUserAdmin",
    pwd: "abc123",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
  }
)

2. 先將服務停下來:
$ sudo service mongod stop

3. 設定 /etc/mongod.conf 裡的 net.bindIp。伺服器綁定的 IP,可將 net.bindIp 設定為 0.0.0.0 或使用 net.bindIpAll 設定為 true。亦或者設定為 Server 實際使用的 IP 及 127.0.0.1。
net:
  port: 27017
  bindIp: 127.0.0.1,157.23.???.???

4. 設定 /etc/mongod.conf 裡的 security.authorization 為 enabled,啟用帳密限定連線。
security:
  authorization: enabled

5. 啟動服務:
$ sudo service mongod start

6. 連線時,需要輸入帳號及密碼
$ mongo -u myUserAdmin -p abc123

如此就可以透過 Robo 3T 從遠端連線管理 MongoDB 了。

另外,可以參考這篇使用 ufw 防火牆去限定連線來的 ip。記得要開啟必要的 ports:
$ sudo ufw allow OpenSSH
$ sudo ufw allow from 你個人電腦的IP to any port 27017
$ sudo ufw allow http
$ sudo ufw allow https







2019-01-24

使用 docker mongo

啟動 mongo ,將 27017 port bind 到 localhost 的 27017 port,可方便使用 robo 3T 工具(儲存區沒有設定):

$ docker run -d -p 27017:27017 --name 容器名稱  mongo

若要使用 mongo console,依據官方文件(不一定要 bind port):

$ docker run -it --link 容器名稱:mongo --rm mongo mongo --host mongo test

使用 robo 3T 是比較方便的。
若要使用 local 磁碟當儲存區:

mkdir ~/data
sudo docker run -d -p 27017:27017 -v ~/data:/data/db mongo



FB 留言