欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-10-29 09:27:08
写在前面 mongodb支持的索引种类很多,诸如单键索引,复合索引,多键索引,ttl索引,文本索引,空间地理索引等。同时索引的属性可以具有唯一性,即唯一索引。唯一索引用于...

写在前面

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)
}

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

总结

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