MongoDBを扱うために最低限知っておきたいこと

##概要

以下観点でまとめたMongoDBの操作系簡易リファレンスです

- とりあえずこれだけ知っていればMongoDBをデータストアとしたアプリ開発に入れる
- 開発Teamメンバーに知っておいてほしいこと

##前提
- MongoDBがセットアップ済みであること
- 参照、更新コマンド、クエリセレクタは最低限使えること

##環境
| 使用環境  | バージョン|
|:----|:------------------:|
|CentOS|7.6|
|MongoDB server|4.0.5|

##状態確認
###データベース一覧
```
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```

###データベース切り替え（新規作成）
```
> use testdb
switched to db testdb
```

###コレクション一覧
```
> show collections
company
department
user
```

##初期化関連
###コレクションtruncate
```
> db.user.remove({})
```
###コレクション削除
```
> db.user.drop()
```

###データベース削除
drop対象を指定した状態で実行

```
> use testdb
> db.dropDatabase()
```

##設計・パフォーマンスチューニング
###問い合わせプラン確認
```
> db.user.find({name:"koda"}).explain()
```

###index確認
```
> db.user.getIndexes()
```
###index作成（通常）
<font color="#666">　value値 1:昇順、-1:降順</font>

```
> db.user.createIndex({name: 1})
```

###index作成（ユニーク）
```
> db.user.createIndex({name: 1}, {unique: true})
```

###index作成（マルチフィールド）
```
> db.user.createIndex({name: 1, sex: 1})
```

##ヘルプ・コマンド確認
###ヘルプ
```
> help
        db.help()                    help on db methods
        db.mycoll.help()             help on collection methods
        sh.help()                    sharding helpers
（省略）
```

###コマンド確認
<font color="#666">　tab補完が使える</font>

#####DBコマンド
```
> db.
db.adminCommand(               db.fsyncLock(                  db.getWriteConcern(            db.revokePrivilegesFromRole(
db.aggregate(                  db.fsyncUnlock(                db.grantPrivilegesToRole(      （省略）
```

#####コレクションコマンド
```
> db.user.
db.user.addIdIfNeeded(              db.user.getCollection(              db.user.mapReduce(
db.user.aggregate(                  db.user.getDB(                      db.user.propertyIsEnumerable
（省略）
```

##コマンドライン実行
###直接実行
```sh
$ echo $SHELL
/bin/bash

$ mongo --host localhost --quiet testdb --eval 'db.user.find()'
{ "_id" : ObjectId("5c7768915fd5d6ec50b38477"), "uid" : 1, "name" : "koda", "sex" : "男" }
{ "_id" : ObjectId("5c7768915fd5d6ec50b38478"), "uid" : 2, "name" : "mori", "sex" : "男" }
{ "_id" : ObjectId("5c7768915fd5d6ec50b38479"), "uid" : 3, "name" : "kubo", "sex" : "男" }
```

###jsを実行
#####jsファイルを準備

```js:find.js
var users = db.user.find();
users.forEach(function(user) {
	if (user) {
		print(user._id + '\t' + user.name + '\t' + user.sex);
	}
});
```

#####実行

```sh
$ mongo --quiet testdb ./find.js
5c7768915fd5d6ec50b38477        koda    男
5c7768915fd5d6ec50b38478        mori    男
5c7768915fd5d6ec50b38479        kubo    男
```

##バックアップ、レストア
###ダンプ
```sh
$ mongodump --archive=/tmp/testdb.20190228.gz --gzip --db testdb
```

###レストア
対象DBがない状態で実行

```
> show dbs
admin   0.000GB
config  0.000GB
local   0.000GB
```
```sh
$ mongorestore --gzip --archive=/tmp/testdb.20190228.gz --db testdb
```

##エクスポート・インポート
###エクスポート
```sh
$ mongoexport --db testdb --collection user --out /tmp/user.json
```

###インポート
```sh
$ mongoimport --db testdb_new --collection user --file /tmp/user.json
```

## シーケンス
オートインクリメント用の関数を作成しRDBのシーケンスのように利用する

```
> use testdb
```
countersコレクションを作成しておく

```
> db.counters.insert(
   {
      _id: "userid",
      seq: 0
   }
)
```
```
> db.counters.find()
{ "_id" : "userid", "seq" : 0 }
```

オートインクリメント関数を作成

- 永続的に使える状態にするため、db.system.jsに登録しておく
- Mongo shell上で定義した変数やfunctionはログオフで消えてしまうため



```

> db.system.js.save({_id:'my_seq', value:function (name) {
     var ret = db.counters.findAndModify(
            {
              query: { _id: name },
              update: { $inc: { seq: 1 } },
              new: true
            }
     );
     return ret.seq;
  }
});
```
確認のためにログオフ

```
> exit
bye
```
再接続

```
$ mongo
```


```
> use testdb
```
登録したスクリプトをロード

```
> db.loadServerScripts()
```

シーケンス取得

```
> my_seq('userid')
1
> my_seq('userid')
2
> my_seq('userid')
3
```

実際に使う場合はこんな感じ

```
> db.users.insert({_id: my_seq('userid'), name: "seq_test_user"} )
> db.users.find()
{ "_id" : 4, "name" : "seq_test_user" }
```


## ユーザ認証
スーパーユーザを準備したうえでアプリ用ユーザを作成するイメージ
###ユーザ管理者の登録
```conf:/etc/mongod.conf
#security:
```

```
> use admin
```

```
> db.createUser({
  user:"admin",
  pwd:"manager",
  roles:[{ role:"userAdminAnyDatabase", db:"admin" }]
})
```

###再起動
認証設定を有効にする

```conf:/etc/mongod.conf
security:
  authorization: enabled
```

```sh
# systemctl restart mongod
```

###アプリ用ユーザ登録
ユーザ管理者で再接続

```sh
$ mongo -u "admin" -p "manager" --authenticationDatabase="admin"
```
```
> use testdb
```
```
> db.createUser(
  {
    user: "airuser",
    pwd:"airuser",
    roles:[
       {role:"readWrite",  db:"testdb"}
    ]
  }
)
```

cf. [Built-In Roles](https://docs.mongodb.com/manual/reference/built-in-roles/)

###ロール変更
```
> use testdb
```
```
> db.updateUser(
  "airuser",
  {
    roles:[
      {role: "readWrite",db: "testdb"}
    ]
  }
)
```

###登録済みユーザ確認
```
> use admin
```
```
> db.system.users.find()
```

###接続確認
アプリ用ユーザで接続

```sh
$ mongo --host localhost -u "airuser" -p "airuser" --authenticationDatabase="testdb"
```

許可DBだけ見える

```
> show dbs
testdb  0.000GB
```

## おまけ
###PHPでアクセス
ユーザ認証ありの状態で検証
#####ファイル準備
```php:mongo.php
<?php
// DBへ接続
$mongo = new MongoClient("mongodb://localhost:27017/testdb",
         array("username" => "airuser", "password" => "airuser"));
// データベースを指定
$db = $mongo->selectDB("testdb");
// コレクションを指定
$coll = $db->selectCollection("user");
// コレクションのドキュメントを取得
$docs = $coll->find(array('uid' => 1));
// 表示
foreach ($docs as $id => $obj) {
    print_r($obj);
}
?>
```
#####実行
```
$ php mongo.php
Array
(
    [_id] => MongoId Object
        (
            [$id] => 5c7787e73cf5a95585ea27f7
        )

    [uid] => 1
    [name] => koda
    [sex] => 男
)
```

###フィールドを縦に展開
利用頻度が高いですよね

```
> db.user.find().pretty()
{
        "_id" : ObjectId("5c7768915fd5d6ec50b38477"),
        "uid" : 1,
        "name" : "koda",
        "sex" : "男"
}
```
- cf.
	- MySQL: SQLの最後に \G
	- PostgreSQL: メタコマンド \x

##参考
- Qiita
	- [MongoDBチートシート](https://qiita.com/morrr/items/8bcb5b0fc643267d6bcf)
	- [MongoDB超入門](https://qiita.com/saba1024/items/f2ad56f2a3ba7aaf8521)
	- [mongoDBよく使うコマンド集（随時更新）](https://qiita.com/AkihiroTakamura/items/667159eaa8d9e39de6dd)
	- [MongoDB 4.0 での互換性の変更](https://qiita.com/kabao/items/e6796d5f039890dc2df3)
	- [MongoDBのデータ構造な簡単な操作など](https://qiita.com/yuji0602/items/c55e2cb75376fd565b4e)
	- [MongoDBコマンド一覧（自分用メモ）](https://qiita.com/k-staging/items/a386d272abb2c9b92f1a)
	- [mongo shellの操作まとめ](https://qiita.com/rubytomato@github/items/390cdead677a851550e8)
	- [MongoDBのユーザ管理](https://qiita.com/t2hk/items/45a04875dc6d8be9b004)
- [MongoDB(本家)](https://docs.mongodb.com/)
	- [Create an Auto-Incrementing Sequence Field](https://docs.mongodb.com/v3.0/tutorial/create-an-auto-incrementing-field/)