MongoDB のトランザクションには様々な制約がある。
ここではそれらについて整理する。
コレクションの作成はできない
MongoDB では存在しないコレクションに対して insert を行うことで自動的にコレクションが生成されるが、トランザクションの途中ではこれができない。
mongo-set:PRIMARY> session = db.getMongo().startSession()
session { "id" : UUID("5538d59a-3e07-4760-8dba-f4565cd49971") }
mongo-set:PRIMARY> books = session.getDatabase('sandbox').books;
sandbox.books
mongo-set:PRIMARY> session.startTransaction()
mongo-set:PRIMARY> books.insert({name: 'dummy data'})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 263,
"errmsg" : "Cannot create namespace sandbox.books in multi-document transaction."
}
})
mongo-set:PRIMARY> session.abortTransaction()
mongo-set:PRIMARY> books.insert({name: 'first book'})
WriteResult({ "nInserted" : 1 })
上記を見ると、 session.startTransaction()
の直後の books コレクションへの insert で失敗している。
その後、 session.abortTransaction()
でトランザクションを終了してから同様の処理をすると、こちらは成功している。
つまり、トランザクションを使うのであれば、その中で利用する可能性のあるコレクションについては、開始時点で1件もドキュメントがないとしてもコレクションの枠だけは先に作っておく必要がある。
同様にコレクションの削除やインデックスの追加、削除等もできない(が、トランザクションの途中でコレクションを削除しようとはしないと思う)。
count() を実行できない
トランザクションの途中での非 CRUD は基本的にできない。
たとえば、わかりやすいところだと count()
でエラーが起きる。
mongo-set:PRIMARY> items = session.getDatabase('sandbox').items;
sandbox.items
mongo-set:PRIMARY> db.items.count()
3
mongo-set:PRIMARY> items.count()
3
mongo-set:PRIMARY> session.startTransaction()
mongo-set:PRIMARY> db.items.count()
3
mongo-set:PRIMARY> items.count()
2019-12-19T00:42:14.301+0900 E QUERY [js] uncaught exception: Error: count failed: {
"operationTime" : Timestamp(1576683733, 1),
"ok" : 0,
"errmsg" : "Cannot run 'count' in a multi-document transaction. Please see http://dochub.mongodb.org/core/transaction-count for a recommended alternative.",
"code" : 263,
"codeName" : "OperationNotSupportedInTransaction",
"$clusterTime" : {
"clusterTime" : Timestamp(1576683733, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
} :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DBQuery.prototype.count@src/mongo/shell/query.js:376:11
DBCollection.prototype.count@src/mongo/shell/collection.js:1368:12
@(shell):1:1
少し冗長だが、上記を見るとトランザクション開始前は items.count()
で件数を取得できるのに、トランザクション開始後に同じコマンドを実行するとエラーになっている。
一方でセッションから独立している db.items.count()
はトランザクションの開始前後を問わず件数を返している。
件数の取得
count()
が使えないので件数が取得できないかというと、そうではない。
MongoDB 4.0.3 以降に用意されている countDocuments()
を使えばトランザクションの途中で件数を取得できる。
mongo-set:PRIMARY> session.startTransaction()
mongo-set:PRIMARY> items.countDocuments({})
3
トランザクション中の count()
の挙動
先ほど、 items.count()
はダメだが db.items.count()
はできると書いたが、トランザクション内で件数の増減は反映される。
mongo-set:PRIMARY> db.items.count()
3
mongo-set:PRIMARY> db.items.distinct('name')
[ "item01", "item02", "item03" ]
mongo-set:PRIMARY> items.count()
3
mongo-set:PRIMARY> items.distinct('name')
[ "item01", "item02", "item03" ]
mongo-set:PRIMARY> session.startTransaction()
mongo-set:PRIMARY> items.insert({name: 'new item'})
WriteResult({ "nInserted" : 1 })
mongo-set:PRIMARY> db.items.count()
4
mongo-set:PRIMARY> db.items.distinct('name')
[ "item01", "item02", "item03" ]
mongo-set:PRIMARY> items.count()
// エラー出力(省略)
mongo-set:PRIMARY> items.distinct('name')
[ "item01", "item02", "item03", "new item" ]
mongo-set:PRIMARY> session.abortTransaction()
mongo-set:PRIMARY> db.items.count()
3
mongo-set:PRIMARY> db.items.distinct('name')
[ "item01", "item02", "item03" ]
mongo-set:PRIMARY> items.count()
3
mongo-set:PRIMARY> items.distinct('name')
[ "item01", "item02", "item03" ]
db.items.count()
の結果はトランザクションの途中でもリアルタイムに反映されているが db.items.distinct()
の方には反映されていない。
この結果を考えると、トランザクションの途中では 最終的にコミットされるかロールバックされるかわからない件数の増減が反映されてしまう ため、注意が必要である。
これはトランザクションを実行している mongo shell とは別に mongo shell を用意しても同様である。