(目录)

数据校验

MongoDB可以在文档插入或更新时,根据设定的验证规则对数据进行检查,如果不符合设定的规则,则会出现报错。

数据校验的注意事项

  • 不能在admin,localconfig数据库中进行数据校验
  • 不能在系统集合(system.*)中进行数据校验
  • 使用者必须拥有collMod操作权限

数据校验的设定方式

在MongoDB中,可以在创建集合时设置数据校验规则,也可以在集合创建后设置数据校验规则,规则的设定分为两种模式:JSON Schema验证模式和查询表达式验证模式

校验规则设定模式

  • JSON Schema验证模式 可以用来规范集合必须具有哪些字段,以及字段类型。如果是数字类型,还可以规定最大值和最小值。从MongoDB3.6版本开始,官方支持这种模式,也是目前建议使用的模式。 在使用这种验证模式时,将validator参数与$jsonSchema操作符搭配使用
  • 查询表达式验证模式 可以搭配查询操作符指定验证的规则,如使用$type来规范字段类型,使用$regex来规范字符串有哪些字节,使用$in来规范必须是哪些值中的一个等。

不论以上哪种验证模式,都可以加上validationAction参数来处理违反验证规则的文档,该参数可以设置为以下两种值

  • error:默认值。表示如果插入或更新违反验证规则的文档,则MongoDB会自动报错
  • warn:如果插入或更新违反验证规则的文档,则MongoDB不会报错,但会把错误信息记录到日志中

校验规则设置方法

创建集合时设置

db.createCollection()时,可以通过加上参数validator来建立验证规则。

集合创建后设置

在集合创建之后,还可以通过db.runCommand()来建立验证规则,在使用这种方法建立验证规则是,不会对现有的文档进行验证,当这些文档被更新是,才会进行验证。 使用此方法时,可以加上validationLevel参数,从而限制MongoDB在更新时验证规则对现有文档的严格程序。可以设置以下三种值

  • off: 关闭校验规则
  • strict: 默认值,表示MongoDB会对文档中的所有新增或更新操作进行验证
  • moderate: 表示MongoDB仅对已符合验证规则的现有文档进行新增,更改验证。对不符合验证规则的现有文档,MongoDB不会再检查其操作是否符合验证规则

示例

# db.createCollection() 搭配JSON Schema验证模式
# foodColor集合必须满足以下条件
# 需要具有name,box_size,dyes三个字段
# name字段的值需要是字符串
# box_size的值只能是3,4,6其中之一
# dyes的值是一个数组,数组中需要包含至少一个元素,数据的元素不能重复。
# 对于dyes数组中的元素,要求如下
## 需要有size和color两个字段
## 不能增加其他字段
## size字段的值只能是small,medium,large其中之一
## color字段的值需要是字符串
db.createCollection ( "foodColor",
{
    validator:
    {
        $jsonSchema:
      {
        bsonType: "object",
        required: ["name", "box_size", "dyes"],
        properties:
        {
            _id: {},
            name: {
                bsonType: ["string"],
                description: "'name' is a required string"
            },
            box_size: {
                enum: [3, 4, 6],
                description: "'box_size' must be one of the values listed and is required"
            },
            dyes: {
                bsonType: ["array"],
                minItems: 1, // each box of food color must have at least one color
                uniqueItems: true,
                items: {
                    bsonType: ["object"],
                    required: ["size", "color"],
                    additionalProperties: false,
                    description: "'items' must contain the stated fields.",
                    properties: {
                        size: {
                          enum: ["small", "medium", "large"],
                          description: "'size' is required and can only be one of the given enum values"
                                },
                        color: {
                          bsonType: "string",
                          description: "'color' is a required field of type string"
                                }
                    }
                }
            }
        }
      }
    }
})
# db.runCommand()搭配查询表达式验证模式
# contacts集合,必须满足以下条件之一:phone的类型是字符串,email中的字符需要以@mongod.com结尾,status值必须是Unknown或者Incomplete之一
db.runCommand( 
{
    collMod:"contacts",
    validator: 
    { $or:
      [
         { phone: { $type: "string" } },
         { email: { $regex: /@mongodb\.com$/ } },
         { status: { $in: [ "Unknown", "Incomplete" ] } }
      ]
   }
} )

原子性操作

在MongoDB中,原子性操作是指在保存文档时,要么全部保存,要么全部回滚。当一个文档在进行写操作时,其他对于此文档的操作是不可以进行的。MongoDB提供的原子性操作包含文档的保存、修改和删除。 MongoDB4.0之前的版本支持单个文档的原子性操作;4.0之后的版本支持在副本集的架构里实现多个文档的事务。

MongoDB中的锁

MongoDB通过锁机制来避免并发操作

  • 当一个使用者对文档进行读操作时,会取得一个“读”锁。此时,其他使用者可以读此文档,但不可以对此文档进行写操作
  • 当一个使用者对文档进行写操作时,会取得一个“写”锁。此时,其他使用者不可以对此文档进行读写操作

常用修改操作符

MongoDB会对文档进行隔离性的写操作,同时还可以搭配一些操作符来对文档进行修改。以下是常用的7种修改操作符

修改字段

  • $set 修改指定文档,如果没有找到指定的文档,则创建一个新文档。
# 初始值
db.products.find({_id:100}).pretty()
{
        "_id" : 100,
        "sku" : "abc123",
        "quantity" : 250,
        "instock" : true,
        "reorder" : false,
        "details" : {
                "model" : "14Q2",
                "make" : "xyz"
        },
        "tags" : [
                "apparel",
                "clothing"
        ],
        "ratings" : [
                {
                        "by" : "ijk",
                        "rating" : 4
                }
        ]
}


# 更新
db.products.update(
   { _id: 100 },
   { $set:
      {
        quantity: 500,
        details: { model: "14Q3", make: "xyz" },
        tags: [ "coats", "outerwear", "clothing" ]
      }
   }
)

## 结果
db.products.find({_id:100}).pretty()
{
        "_id" : 100,
        "sku" : "abc123",
        "quantity" : 500,
        "instock" : true,
        "reorder" : false,
        "details" : {
                "model" : "14Q3",
                "make" : "xyz"
        },
        "tags" : [
                "coats",
                "outerwear",
                "clothing"
        ],
        "ratings" : [
                {
                        "by" : "ijk",
                        "rating" : 4
                }
        ]
}
  • $unset 删除指定的字段
# 删除tags和reorder字段
db.products.update(
        {_id : 100},
        {$unset : {tags : "",reorder : ""}})
# 结果
 db.products.find({_id:100}).pretty()
{
        "_id" : 100,
        "sku" : "abc123",
        "quantity" : 500,
        "instock" : true,
        "details" : {
                "model" : "14Q3",
                "make" : "xyz"
        },
        "ratings" : [
                {
                        "by" : "ijk",
                        "rating" : 4
                }
        ]
}
  • $inc 在原数值基础上增加数值大小
# quantity的值-2
db.products.update({_id:100},{$inc:{quantity:-2}})
# 结果
db.products.find({_id:100}).pretty()
{
        "_id" : 100,
        "sku" : "abc123",
        "quantity" : 498,
        "instock" : true,
        "details" : {
                "model" : "14Q3",
                "make" : "xyz"
        },
        "ratings" : [
                {
                        "by" : "ijk",
                        "rating" : 4
                }
        ]
}
# 将sku字段名称更改为StockKeepingUnit
db.products.update({_id:100},{$rename:{"sku":"StockKeepingUnit"}})
# 结果
db.products.find({_id:100}).pretty()
{
        "_id" : 100,
        "quantity" : 498,
        "instock" : true,
        "details" : {
                "model" : "14Q3",
                "make" : "xyz"
        },
        "ratings" : [
                {
                        "by" : "ijk",
                        "rating" : 4
                }
        ],
        "StockKeepingUnit" : "abc123"
}

修改数组

  • $push 在数组中插入值
# 插入单个值
db.students.update(
   { _id: 1 },
   { $push: { scores: 89 } }
)
# 使用$each插入多个值
db.students.update(
   { name: "joe" },
   { $push: { scores: { $each: [ 90, 92, 85 ] } } } 
)
  • $pull 在数组中删除值
# 初始值
db.stores.find();
{ "_id" : 1, "fruits" : [ "apples", "pears", "oranges", "grapes", "bananas" ], "vegetables" : [ "carrots", "celery", "squash", "carrots" ] }
{ "_id" : 2, "fruits" : [ "plums", "kiwis", "oranges", "bananas", "apples" ], "vegetables" : [ "broccoli", "zucchini", "carrots", "onions" ] }
# 使用pull删除fruits中apples和oranges的值,以及vegetables中carrots的值
db.stores.update(
    { },
    { $pull: { fruits: { $in: [ "apples", "oranges" ] }, vegetables: "carrots" } },
    { multi: true }
)
# 删除后的结果
db.stores.find()
{ "_id" : 1, "fruits" : [ "pears", "grapes", "bananas" ], "vegetables" : [ "celery", "squash" ] }
{ "_id" : 2, "fruits" : [ "plums", "kiwis", "bananas" ], "vegetables" : [ "broccoli", "zucchini", "onions" ] }
  • $pop 删除数组中的第一个或最后一个值
# 删除_id为1的文档中,fruits数组的最后一个值和vegetables数组的第一个值
db.stores.update({_id:1},{$pop:{"fruits":1,"vegetables":-1}})
# 结果
db.stores.find()
{ "_id" : 1, "fruits" : [ "pears", "grapes" ], "vegetables" : [ "squash" ] }
{ "_id" : 2, "fruits" : [ "plums", "kiwis", "bananas" ], "vegetables" : [ "broccoli", "zucchini", "onions" ] }