旧それなりブログの跡地、画像やスタイルやJSなどが壊れてることがあります。

MongoDBのsparse indexについての雑なまとめ

2014年9月25日

たぶん理解してないので、検証済みの点や参考資料だけを雑に置いておきます。

MongoDB のバージョンは 2.6.4 です。

unique と一緒に指定すると null を重複判定から除外できる?

ググって昔の日本語記事を読むとこういう解説が良く出てきますが、不正確です。
どちらかというと間違いです。

例えば、このようにインデックスを張った時に:

> db.foo.ensureIndex({x:1}, {unique:1, sparse:1})

{x:null} なドキュメントを連続で保存しようとすると、2 回目で重複エラーが発生します:

> db.foo.save({x:null})
> db.foo.save({x:null})
WriteResult({
        "nInserted" : 0,
        "writeError" : {
                "code" : 11000,
                "errmsg" : "insertDocument :: caused by :: 11000 E11000 duplicate key error index: sos_test.sandbox.$x_1  dup key: { : null }"
        }
})

なぜなら、無視されるのは、x が存在しない(キーごと無い)ドキュメントだけだからです。

例えば、以下なら同じドキュメントをいくつでも保存できます:

> db.foo.save({y:2, z:3})  // x が無いから

なお、もし unique 制約のみをつけている場合は、これも 2 回目で重複エラーになります。

注意としては、複数キーに対するインデックスでは sprase 自体が無効になってしまうので、この方法は使えません。

国内海外問わず、”null” という表現にキー自体が存在しないこと含めている場合があるので、調べる時は注意が必要です。

インデックスしないのが sparse

sparse オプションは別に unique のためにあるのではなく、本来は「指定フィールドが存在しない場合にインデックスをしない」というものらしいです。

中身の仕組みを理解していないので、以下雑なメモです:

・抽出判定には影響を与えないっぽい
・sort 順は変わるかもしれない
・速度に対しては多分に影響する

確認する際はインデックスが貼られていることを確認する

動作確認する際は、必ず想定したインデックスが貼られていることを確認してから行った方が良いです。

例えば、貼りたいインデックスに反するデータが既に存在する場合、インデックスするのに失敗します:

> db.sandbox.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "my_db.sandbox"
        }
]
> db.sandbox.save({x:null})
WriteResult({ "nInserted" : 1 })
> db.sandbox.save({x:null})
WriteResult({ "nInserted" : 1 })
> db.sandbox.find()
{ "_id" : ObjectId("54238aaa69d90363b7f2787e"), "x" : null }
{ "_id" : ObjectId("54238aab69d90363b7f2787f"), "x" : null }  // 重複データが既に存在
> db.sandbox.ensureIndex({x:1}, {unique:true, sparse:true})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "ok" : 0,
        "errmsg" : "E11000 duplicate key error index: my_db.sandbox.$x_1  dup key: { : null }",
        "code" : 11000
}
> db.sandbox.getIndexes()
[
        {
                "v" : 1,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "my_db.sandbox"
        }
]

また、当ブログの mongoose に関する記事の最後にも書きましたが:

ensureIndexes は、Schema に整合しないデータ(例えば unique:true なのに重複データが入っているなど)がある場合、該当の設定を無視して Index を全く作らないようです。

少なくとも mongoose-3.8.15 ではこのインデックス失敗をランタイムエラーにはしないので、Schema 上で定義されているインデックスがない状態で動いてしまう可能性があります。

参考リンク

マニュアル: http://docs.mongodb.org/manual/core/index-sparse/
日本語なら何気にPHPのこれが良かった: http://php.net/manual/ja/mongocollection.ensureindex.php