唯一索引这玩意儿,真得好好掂量掂量
在大厂(就是那种用户多、数据量大、需求快速迭代的地方),如果不是对账那种一分钱不能错的业务,想着靠数据库的 UNIQUE INDEX(唯一索引)来拦重复数据,说实话,效果不一定好,伺候它的成本还很高。更好的办法是把去重的主要活儿放应用层,数据库那个唯一索引,能不用就先别用,或者想清楚了再用。 一、 为啥我开始琢磨唯一索引这事儿?因为坑踩了 数据库唯一索引,听着挺靠谱,对吧?保证数据不重复的最后一道防线。以前我也是这么想的,表里哪个字段不让重,随手就给它来个唯一索引。 直到被现实狠狠摩擦了。 很久之前,当我头发还很茂密的时候,给一个千万级的表加个组合唯一索引(比如 tenant_id 和 is_deleted 这俩字段不能重复)。听着简单吧?结果呢,整个变更从头到尾折腾了几天!这期间,主从延迟跟过山车似的,时不时还得担心线上服务会不会抖一下。事儿完了我在想,就为了数据库层面这个“唯一”,搭进去这么多工夫、担这么大风险,值吗? 还有个事儿也挺别扭。 业务上,我们都知道 [email protected] 和 [email protected] 其实是一个邮箱,你注册的时候,应用代码肯定也会把它们统一处理成小写再判断有没有重复。结果呢,数据库里的唯一索引(区分大小写)不认这个账。有时候因为一些历史数据或者旁路数据同步没做好规范化,数据库里就存了两种大小写格式的“同一个”邮箱。这时候,唯一索引它要么“眼瞎”发现不了这种业务上的重复,要么在你修数据的时候,因为它那死板的规矩,反而碍手碍脚。 更别提业务迭代了。 比如,以前光是“邮箱唯一”就行,现在要改成“租户 ID+邮箱唯一”。好家伙,应用代码得改吧?数据库的唯一索引也得跟着 DROP 旧的 CREATE 新的吧?这两拨操作怎么配合?谁先谁后?万一中间出岔子怎么办?在大表上搞这种操作,每次都跟拆炸弹似的,提心吊胆。 就这些事儿搞得我不得不琢磨:数据量大、并发高、需求又变得快,唯一索引这一套,是不是该重新掂量掂量了?它带来的麻烦,是不是已经比它的好处多了? 这篇文章,就是想跟大家伙儿聊聊我的反思。 二、 UNIQUE INDEX:我们为啥那么信它? 在吐槽之前,咱也得公平点,说说唯一索引为啥那么招人待见,它确实有几个看上去不错的点: 数据不跑偏的最后保险: 防止数据重复的最后一道关卡。 上手简单: 建表的时候或者后来加个 DDL,几行 SQL 就搞定了。 表结构一看就懂: Schema 里标着呢,这字段不能重。 顺便还能快点查: 反正也是个索引,按这个键查数据能快点。 这些好处,在小项目或者数据量不大、业务不复杂的时候,确实挺香。但一到大数据量+快速迭代“修罗场”,情况就变了。 三、 大厂滤镜下的UNIQUE INDEX:那些“好处”还好使吗? 接下来咱们挨个盘盘上面说的那些“好处”,看看在大厂这环境下,它们是不是还那么“美”。 “最后保险”?这保险靠谱吗?保的是啥险? 业务上的“重复”它认不全啊!就像前面说的邮箱大小写,还有手机号带不带+86,用户名清不清除特殊字符……这些业务上才认的“一样”,数据库那简单粗暴的“字节必须一样”的唯一索引根本管不过来。它防不住业务层面的“逻辑重复”。 应用层反正要干活。 既然这些复杂的“一样不一样”都得在应用代码里判断(总不能直接把数据库报错丢给用户吧?),那应用层才是真正保证“业务数据不重复”的主力军。数据库那个唯一索引,充其量是个标准可能还跟业务不一致的“辅警”。 分布式系统里它就是个“本地保镖”。在分布式场景下一旦分表,表内的唯一索引,管不了全局唯一性。全局唯一还得靠 ID 生成服务或者应用层的全局校验。这时候,数据库本地那点“保险”作用就更小了。 这个“最后保险”既可能保不到点子上,覆盖面也有限,全指望它优点悬。 “上手简单”?一次上线,一周折腾 新表新加个唯一索引,确实就一条 SQL。但更多的时候是给已经跑了好久、数据堆成山的旧表改规则。你想给千万行的表改个唯一索引(比如从一个字段唯一改成俩字段组合唯一),可能就是几分钟的锁表!在线 DDL 工具,也只是让你不用停服务,但整个过程照样漫长、耗资源、有风险。 敏捷?快不起来啊!在快速迭代+多区域同步+合规要求的场景下。数据库这儿一个唯一索引的变更就要卡你好几天,啥敏捷都白搭。 所以开始那一下“简单”,跟后来改起来的“要老命”比,简直是钓鱼。 “表结构一看就懂”?懂的可能跟实际要的不一样啊! 唯一索引在表结构里写着,是,算是一种“技术文档”。可是“文档”可能误导人,如果这个唯一索引定义的“唯一”跟业务上实际的、更复杂的唯一规则对不上(比如大小写问题),那这份“文档”不光没用,还可能误导后来的开发。 如果改这份“文档”(就是改唯一索引)就要经历九九八十一难,那我们为啥不把业务规则好好写在真正的设计文档、Wiki 或者代码注释里呢?那些地方改起来可方便多了。 “顺便还能快点查”?为了这点醋,才包的这顿饺子? 这是个很常见的误解,或者说是一个被过分强调的“附加值”。如果你只是想让某个字段或某几个字段的查询快一点,你完全可以给它们建一个普普通通的、非唯一的索引(Non-Unique Index)啊!非唯一索引照样能嗖嗖地提高查询速度,而且它还没有唯一性约束带来的那些写入开销、DDL 痛苦和业务逻辑的死板限制。 ...