当前位置: 移动技术网 > IT编程>数据库>MongoDB > MongoDB中唯一索引(Unique)的那些事

MongoDB中唯一索引(Unique)的那些事

2019年03月18日  | 移动技术网IT编程  | 我要评论

写在前面

mongodb支持的索引种类很多,诸如单键索引,复合索引,多键索引,ttl索引,文本索引,空间地理索引等。同时索引的属性可以具有唯一性,即唯一索引。唯一索引用于确保索引字段不存储重复的值,即强制索引字段的唯一性。缺省情况下,mongodb的_id字段在创建集合的时候会自动创建一个唯一索引。本文主要描述唯一索引的用法。

关于什么是索引以及唯一索引这里就不做说明了,不清楚的可以自行谷歌或者百度。是什么引起我写这篇文章呢,这来自于之前项目中的一个问题。

我们用的是mongodb数据存储用户信息,用户表中曾经用户注册是通过手机号注册的,所以很理所当然的给手机号加上了唯一索引(unique),这是没有什么毛病。后期,我们需求改了。你也可以想到变成了既可以手机号注册又可以邮箱注册,这个时候由于手机号加了unique索引,事实上这时候是会出现问题的。

func init() {
 phoneindex := mgo.index{
 key: []string{"phone"},
 unique: true,
 }

 col := db.collection(&user{})
 col.ensureindex(phoneindex)
}

当然这问题其实也容易想到,当用户通过邮箱注册此时手机号填空的时候,第一次没什么问题,下个用户再以这种方式注册的时候便会提示建立在phone上的索引值重复,很正常嘛,因为插入了两个空值,注意这里是空字符串,而不是null。

于是我们尝试修改,由于mongodb是文档型灵活的数据库,少插多插一两个字段不受影响,所以我们尝试修改user实体phone字段的入口,当phone是空字符串的时候,不让插入此字段。于是,我们便在phone字段中加入了omitempty标签(我们微服务用go语言写的)。下面展示user一部分内容:

type user struct {
 email  string `bson:"email"`
 salt  string `bson:"salt"`
 phone  string `bson:"phone,omitempty"`
 idcard string `bson:"idcard"`
 realname string `bson:"realname"`
 authstatus int `bson:"auth_status"`
}

可以看到phone字段后加了omitempty标签,表示当该字段为空的时候不插入。这还是会出现问题,那么既然还是会出问题为什么会想到这么解决呢?这源于对mysql的使用经验,习惯性的以为mongodb和mysql那样,对null的值会不做其索引。也就是说,在mysql中,若在多条记录中phone值为null是被允许的。

上面那种做法,还是会报错,提示插入了重复的值,只不过这时不是空字符串,而是null。所以有时候就不要把mysql那套拿来了,mysql是可以的,但mongo不行。mongo还是会对该条记录索引,即使该字段为被插入。

我喜欢看官方文档,下面给出mongodb官方文档说明:

if a document does not have a value for the indexed field in a unique
index, the index will store a null value for this document. because of
the unique constraint, mongodb will only permit one document that
lacks the indexed field. if there is more than one document without a
value for the indexed field or is missing the indexed field, the index
build will fail with a duplicate key error.

其实已经说得很清楚了,稍微会点英语应该都能看懂,下面还是给出翻译版:

如果文档没有唯一索引中索引字段的值,则索引将为此文档存储null值。由于唯一约束,mongodb只允许一个缺少索引字段的文档。如果有多个文档没有索引字段的值或缺少索引字段,则索引构建将失败并出现重复键错误。

也就是说这个字段哪怕在文档中没有,那么该字段将会存null值,该字段上也不能同时出现两个null值,这就是为什么上面那种做法还是行不通的原因,其实上面那种做法也打破了数据结构,虽然手机号未填,但数据库中也不应该缺少这个字段,尽管是非关系数据库,毕竟还得考虑下业务设计。

解决方式

是不是就没有解决方式了呢?当然有,mongo提供了sparse index,被翻译为稀疏索引。下面是创建稀疏索引的例子:

db.getcollection("test").createindex( { "phone": 1 }, { sparse: true })

执行上面的语句后,不会去索引不存在phone字段的文档。也就是说存在才对其索引,那么此时和unique索引结合起来就可以派上用场了。unqiue是唯一,sparse是存在才索引。所以,当phone或email为空的时候我们可以不将其插入这是可以实现的。

db.getcollection("test").createindex( { "phone": 1 }, { sparse: true,unique: true } )

上面是是mongo shell语法,通常我们一般通过代码中建立索引,修改如下(当然user结构体中phone字段omitempty标签还是要有的):

func init() {
 phoneindex := mgo.index{
 key: []string{"phone"},
 unique: true,
 sparse: true,
 }

 col := db.collection(&user{})
 col.ensureindex(phoneindex)
}

但是这又正如我们前面说的那样,打破了数据原有的数据结构。哎,有得有得。当然我们还可以从业务层面去解决,比如注册时对其查询等操作,当然会耗一定性能,不管你是那空间换时间,还是拿时间换空间总得付出一个,别做一个太贪心的人。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网