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

逻辑潮汐
• 阅读 8663

写在前面

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

我们用的是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)
}

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

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
SQL主键简单表述
主键(PRIMARYKEY约束):PRIMARYKEY约束唯一标识数据库表中的每条记录。主键就是唯一的,其是索引的一种,并且是唯一性索引的一种。其实主键就像我们的身份证一样,每一个主键的id就表示着一个特定的一个行,或者说那一行信息,那一行数据。我们select查询数据,如果我们知道主键是多少,那么我们直接加到where条件就ok了。
Easter79 Easter79
3年前
sqlserver2005创建唯一约束的方法
对于一个表中非主键列的指定列,唯一(UNIQUE约束|:强制非主键上的实体完整性的约束。UNIQUE约束确保未输入重复值,并创建一个索引以增强性能。)约束确保不会输入重复的值。例如,在employee表中emp\_id列是主键,可以定义一个唯一约束来要求表中社会安全号码(ssn)列的项是唯一的。在数据库关系图中,可以使用"索引/键"属性页创建、
Wesley13 Wesley13
3年前
java架构之路
说一下mysql比较宏观的面试,具体咋写sql的这里就不过多举例了。后面我还会给出一个关于mysql面试优化的试题,这里主要说的索引和BTree结构,很少提到我们的集群配置优化方案。1.索引是什么?有什么作用以及缺点  答:索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。也可以理解为索引就
Wesley13 Wesley13
3年前
mysql存在则更新,不存在则插入
INSERTINTOONDUPLICATEKEYUPDATE与REPLACEINTO,两个命令可以处理重复键值问题,在实际上它之间有什么区别呢?前提条件是这个表必须有一个唯一索引或主键。unique1、REPLACE发现重复的先删除再插入,如果记录有多个字段,在插入的时候如果有的字段没有赋值,那么新插入的记录这些字段
Wesley13 Wesley13
3年前
MySQL表介绍
MySQLInnoDB表介绍一、索引组织表在InnoDB引擎中,表都是根据主键顺序存放的。这种存储方式称为索引组织表,在InnoDB引擎中,每张表都有逐渐。如果没有显示定义主键,则引擎会按照以下方式选择或创建主键。(1)、判断表是否有非空唯一索引,如果有,则该字段为主键。如果有多个非空唯一索引,则选择第一个定义的非空索引字段作为
Wesley13 Wesley13
3年前
MySQL 索引(3)
什么是索引?索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。比如想从字典中查询某一个字,我们可以通过偏旁、或者拼音来快速定位到要找的页码,这种方式也可以被理解为一种索引。Mysql常用的索引类型类型说明Normal(普通)普通索引,没任何限制。Unique(唯
Wesley13 Wesley13
3年前
Mysql索引最佳实践笔记0524
mysql5.7innodb默认存储引擎一、关于索引二、最佳实践三、避坑实践一、关于索引1.索引的作用提高查询效率数据分组、排序避免回表查询优化聚集查询用于多表join关联查询利用唯一性约束、保证数据唯一性innodb行锁实现索引的“
Stella981 Stella981
3年前
Elasticsearch (1)
创建索引库ES的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于MySQL中的表,或相当于Mongodb中的集合。关于索引这个语:索引(名词):ES是基于Lucene构建的一个搜索服务,它要从索引库搜索符合条件索引数据。索引(动词):索引库刚创建起来是空的,将数据添加到索引库的过程称为索
Wesley13 Wesley13
3年前
MySQL 处理重复数据
有些MySQL数据表中可能存在重复的记录,有些情况我们允许重复数据的存在,但有时候我们也需要删除这些重复的数据。本章节我们将为大家介绍如何防止数据表出现重复数据及如何删除数据表中的重复数据。防止表中出现重复数据你可以在MySQL数据表中设置指定的字段为PRIMARYKEY(主键)或者UNIQUE(唯一)索引来保证数据的唯一性
Wesley13 Wesley13
3年前
Mysql索引优化
1索引的类型UNIQUE唯一索引不可以出现相同的值,可以有NULL值。INDEX普通索引允许出现相同的索引内容。PRIMARYKEY主键索引不允许出现相同的值,且不能为NULL值,一个表只能有一个primary\_key索引。fulltextindex全文索引
3A网络 3A网络
2年前
明明加了唯一索引,为什么还是产生了重复数据?
明明加了唯一索引,为什么还是产生了重复数据?前言前段时间我踩过一个坑:在mysql8的一张innodb引擎的表中,加了唯一索引,但最后发现数据竟然还是重复了。到底怎么回事呢?本文通过一次踩坑经历,聊聊唯一索引,一些有意思的知识点。1.还原问题现场前段时间,为了防止商品组产生重复的数据,我专门加了一张防重表。如果大家对防重表,比较感兴趣,可以看看