淘宝数据库,主键如何设计的?

聊一个实际问题:淘宝的数据库,主键是如何设计的?
某些错的离谱的答案还在网上年复一年的流传着,甚至还成为了所谓的 MySQL 军规。其中,一个最明显的错误就是关于MySQL 的主键设计。
大部分人的回答如此自信:用 8 字节的 BIGINT 做主键,而不要用 INT
这样的回答,只站在了数据库这一层,而没有 从业务的角度 思考主键。主键就是一个自增 ID 吗?

13.1 自增ID的问题

自增 ID 做主键,简单易懂,几乎所有数据库都支持自增类型,只是实现上各自有所不同而已。自增 ID 除了简单,其他都是缺点,总体来看存在以下几方面的问题:
1. 可靠性不高
存在自增 ID 回溯的问题,这个问题直到最新版本的 MySQL 8.0 才修复。
2. 安全性不高
对外暴露的接口可以非常容易猜测对应的信息。比如: /User/1/ 这样的接口,可以非常容易猜测用户 ID 的值为多少,总用户数量有多少,也可以非常容易地通过接口进行数据的爬取。
3. 性能差
自增 ID 的性能较差,需要在数据库服务器端生成。
4. 交互多
业务还需要额外执行一次类似 last_insert_id() 的函数才能知道刚才插入的自增值,这需要多一次的
网络交互。在海量并发的系统中,多 1 SQL ,就多一次性能上的开销。
5. 局部唯一性
最重要的一点,自增 ID 是局部唯一,只在当前数据库实例中唯一,而不是全局唯一,在任意服务器间都是唯一的。对于目前分布式系统来说,这简直就是噩梦。

13.2 业务字段做主键

为了能够唯一地标识一个会员的信息,需要为 会员信息表 设置一个主键。那么,怎么为这个表设置主键,才能达到我们理想的目标呢? 这里我们考虑业务字段做主键。
表数据如下:
在这个表里,哪个字段比较合适呢?

选择卡号(cardno

会员卡号( cardno )看起来比较合适,因为会员卡号不能为空,而且有唯一性,可以用来 标识一条会员记录。
mysql> CREATE TABLE demo .membermaster
-> (
-> cardno CHAR ( 8 ) PRIMARY KEY , -- 会员卡号为主键
-> membername TEXT ,
-> memberphone TEXT ,
-> memberpid TEXT ,
-> memberaddress TEXT ,
-> sex TEXT ,
-> birthday DATETIME
-> );
Query OK, 0 rows affected ( 0.06 sec)

 不同的会员卡号对应不同的会员,字段“cardno”唯一地标识某一个会员。如果都是这样,会员卡号与会员一一对应,系统是可以正常运行的。

但实际情况是, 会员卡号可能存在重复使用 的情况。比如,张三因为工作变动搬离了原来的地址,不再到商家的门店消费了 (退还了会员卡),于是张三就不再是这个商家门店的会员了。但是,商家不想让这个会 员卡空着,就把卡号是“10000001” 的会员卡发给了王五。
从系统设计的角度看,这个变化只是修改了会员信息表中的卡号是 “10000001” 这个会员 信息,并不会影响到数据一致性。也就是说,修改会员卡号是“10000001” 的会员信息, 系统的各个模块,都会获取到修改后的会员信息,不会出现“ 有的模块获取到修改之前的会员信息,有的模块获取到修改后的会员信息,而导致系统内部数据不一致” 的情况。因此,从 信息系统层面 上看是没问题的。
但是从使用 系统的业务层面 来看,就有很大的问题 了,会对商家造成影响。
比如,我们有一个销售流水表( trans ),记录了所有的销售流水明细。 2020 12 01 日,张三在门店购买了一本书,消费了 89 元。那么,系统中就有了张三买书的流水记录,如下所示:

 接着,我们查询一下 2020 12 01 日的会员销售记录:

 如果会员卡“10000001”又发给了王五,我们会更改会员信息表。导致查询时:

这次得到的结果是:王五在 2020 12 01 日,买了一本书,消费 89 元。显然是错误的!结论: 千万不能把会员卡号当做主键。

选择会员电话 或 身份证号

会员电话可以做主键吗?不行的。在实际操作中,手机号也存在 被运营商收回 ,重新发给别人用的情况。
那身份证号行不行呢?好像可以。因为身份证决不会重复,身份证号与一个人存在一一对 应的关系。可 问题是,身份证号属于 个人隐私 ,顾客不一定愿意给你。要是强制要求会员必须登记身份证号,会把很 多客人赶跑的。其实,客户电话也有这个问题,这也是我们在设计会员信息表的时候,允许身份证号和 电话都为空的原因。
所以,建议尽量不要用跟业务有关的字段做主键。毕竟,作为项目设计的技术人员,我们谁也无法预测 在项目的整个生命周期中,哪个业务字段会因为项目的业务需求而有重复,或者重用之类的情况出现。
经验:
刚开始使用 MySQL 时,很多人都很容易犯的错误是喜欢用业务字段做主键,想当然地认为了解业务需求,但实际情况往往出乎意料,而更改主键设置的成本非常高。

13.3 淘宝的主键设计

在淘宝的电商业务中,订单服务是一个核心业务。请问, 订单表的主键 淘宝是如何设计的呢?是自增 ID吗?
打开淘宝,看一下订单信息:

 

从上图可以发现,订单号不是自增 ID !我们详细看下上述 4 个订单号:
1550672064762308113
1481195847180308113
1431156171142308113
1431146631521308113
订单号是 19 位的长度,且订单的最后 5 位都是一样的,都是 08113 。且订单号的前面 14 位部分是单调递增的。
大胆猜测,淘宝的订单 ID 设计应该是:
订单 ID = 时间 + 去重字段 + 用户 ID 6 位尾号
这样的设计能做到全局唯一,且对分布式系统查询及其友好。

 13.4 推荐的主键设计

非核心业务 :对应表的主键自增 ID ,如告警、日志、监控等信息。
核心业务 主键设计至少应该是全局唯一且是单调递增 。全局唯一保证在各系统之间都是唯一的,单调 递增是希望插入时不影响数据库性能。
这里推荐最简单的一种主键设计:改造UUID。
UUID 的特点: 
全局唯一,占用 36 字节,数据无序,插入性能差。
缺点:
满足全局唯一 但无法保证单调递增 
认识 UUID
为什么 UUID 是全局唯一的?
为什么 UUID 占用 36 个字节?
为什么 UUID 是无序的?
MySQL 数据库的 UUID 组成如下所示:
UUID = 时间 +UUID 版本( 16 字节) - 时钟序列( 4 字节) - MAC 地址( 12 字节)
我们以 UUID 值e0ea12d4-6473-11eb-943c-00155dbaa39d举例:
为什么 UUID 是全局唯一的?
UUID 中时间部分占用 60 位,存储的类似 TIMESTAMP 的时间戳,但表示的是从 1582-10-15 00 00 00.00 到现在的100ns 的计数。可以看到 UUID 存储的时间精度比 TIMESTAMPE 更高,时间维度发生重复的概率降低到1/100ns
时钟序列是为了避免时钟被回拨导致产生时间重复的可能性。 MAC 地址用于全局唯一。
为什么 UUID 占用 36 个字节?
UUID 根据字符串进行存储,设计时还带有无用 "-" 字符串,因此总共需要 36 个字节。
为什么 UUID 是随机无序的呢?
因为 UUID 的设计中,将时间低位放在最前面,而这部分的数据是一直在变化的,并且是无序
改造 UUID
若将时间高低位互换,则时间就是单调递增的了,也就变得单调递增了。 MySQL 8.0 可以更换时间低位和时间高位的存储方式,这样UUID 就是有序的 UUID 了。
MySQL 8.0 还解决了 UUID 存在的空间占用的问题,除去了 UUID 字符串中无意义的 "-" 字符串,并且将字符串用二进制类型保存,这样存储空间降低为了16 字节。 
 36个符号去掉4个 "-" 剩下32个符号, 用二进制存储16进制符号需要4比特位, 32 × 4 = 128 位
128 / 8 = 16字节
可以通过 MySQL8.0 提供的 uuid_to_bin 函数实现上述功能,同样的, MySQL 也提供了 bin_to_uuid 函数进行 转化:  (TRUE  )
SET @uuid = UUID();
SELECT @uuid ,uuid_to_bin( @uuid ),uuid_to_bin( @uuid , TRUE );

 通过函数uuid_to_bin(@uuid,true)UUID转化为有序UUID了。全局唯一 + 单调递增,这就是我们想要的主键!

4 、有序 UUID 性能测试
16 字节的有序 UUID ,相比之前 8 字节的自增 ID ,性能和存储空间对比究竟如何呢?
我们来做一个测试,插入 1 亿条数据,每条数据占用 500 字节,含有3个二级索引,最终的结果如下所示:
从上图可以看到插入 1 亿条数据有序 UUID 是最快的,而且在实际业务使用中有序 UUID 业务端就可以生成 。还可以进一步减少 SQL 的交互次数。
另外,虽然有序 UUID 相比自增 ID 多了 8 个字节,但实际只增大了 3G 的存储空间,还可以接受。
在当今的互联网环境中,非常不推荐自增ID作为主键的数据库设计。更推荐类似有序UUID的全局 唯一的实现。 另外在真实的业务系统中,主键还可以加入业务和系统属性,如用户的尾号,机房的信息等。这样的主键设计就更为考验架构师的水平了。
如果不是 MySQL8.0 肿么办?
手动赋值字段做主键!
比如,设计各个分店的会员表的主键,因为如果每台机器各自产生的数据需要合并,就可能会出现主键 重复的问题。
可以在总部 MySQL 数据库中,有一个管理信息表,在这个表中添加一个字段,专门用来记录当前会员编 号的最大值。
门店在添加会员的时候,先到总部 MySQL 数据库中获取这个最大值,在这个基础上加 1 ,然后用这个值 作为新会员的“id” ,同时,更新总部 MySQL 数据库管理信息表中的当 前会员编号的最大值。
这样一来,各个门店添加会员的时候,都对同一个总部 MySQL 数据库中的数据表字段进 行操作,就解决了各门店添加会员时会员编号冲突的问题。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/98287.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于el-input和el-select宽度不一致问题解决

1. 情景一 单列布局 对于上图这种情况&#xff0c;只需要给el-select加上style"width: 100%"即可&#xff0c;如下&#xff1a; <el-select v-model"fjForm.region" placeholder"请选择阀门类型" style"width: 100%"><el-o…

QT C++ 实现网络聊天室

一、基本原理及流程 1&#xff09;知识回顾&#xff08;C语言中的TCP流程&#xff09; 2&#xff09;QT中的服务器端/客户端的操作流程 二、代码实现 1&#xff09;服务器 .ui .pro 在pro文件中添加network库 .h #ifndef WIDGET_H #define WIDGET_H#include <QWidget>…

喜讯 | 数智经营新典范,体验家XMPlus荣获「年度数智经营服务商」

7月27日&#xff0c;“助力运营知识与创新传播”的内容服务平台——运营研究社举行了「2023数字化运营生态大会」&#xff0c;会上正式揭晓了「2023数字化运营生态大奖」的四大榜单&#xff0c;体验家XMPlus荣获「年度数智经营服务商」&#xff01;现场有800运营伙伴齐聚&#…

error:03000086:digital envelope routines::initialization error

项目背景 前端vue项目启动突然报错error:03000086:digital envelope routines::initialization error 我用的开发工具是vscode&#xff0c;node版本是v18.17.0 前端项目版本如下↓ 具体报错如下↓ 报错原因 node版本过高 解决方法 1输入命令 $env:NODE_OPTIONS"--op…

Unexpected mutation of “xxxx“ prop

原因 是因为子级修改了父级的数据&#xff0c;所以eslint执行的时候报了这个错 修复方式 1 如果是弹窗等组件&#xff0c;可以根据功能进行修改&#xff0c;比如我这块用的 element ui 的 dialog&#xff0c;便可以改成这样 使用 model-value 代替 修复方式 2 新建子组件…

潜艇来袭(Qt官方案例-2维动画游戏)

一、游戏介绍 1 开始界面 启动程序&#xff0c;进入开始界面。 2 开始新游戏 点击菜单&#xff1a;File》New Game &#xff08;或者CtrlN&#xff09;进入新游戏。 开始新游戏之后&#xff0c;会有一个海底的潜艇&#xff0c;和水面舰艇对战。 计算机&#xff1a;自动控制…

zabbix配置钉钉告警、和故障自愈

钉钉告警python脚本 cat python20 #!/usr/bin/python3 #coding:utf-8 import requests,json,sys,os,datetime # 机器人的Webhook地址 webhook"钉钉" usersys.argv[1] textsys.argv[3] data{"msgtype": "text","text": {"conten…

MetInfo5.0文件包含漏洞

MetInfo历史版本与文件 环境在这里下载&#xff0c;使用phpstudy搭建 我们来看到这个index.php&#xff0c;如下图所示&#xff0c;其中定义了fmodule变量与module变量&#xff0c;其中require_once语句表示将某个文件引入当前文件&#xff0c;在这个代码中&#xff0c;通过r…

matlab函数 状态空间系统ss、能控性矩阵ctrb、矩阵的秩rank、能控标准型canon、零极点配置place、系统极点pole等函数(线性定常系统)

matlab函数 能控性矩阵ctrb、能控标准型canon、零极点配置place 第一章&#xff0c;线性定常系统 ss 如果已知线性定常系统的ABCD四个矩阵&#xff0c;可以得到状态空间系统 其他更具体的用法请直接看帮助文档。 用法&#xff1a;ss(A,B,C,D) 假如 可以输入 A [-1.5,-2…

Vue——vue3中的ref和reactive数据理解以及父子组件之间props传递的数据

ref()函数 这是一个用来接受一个内部值&#xff0c;返回一个响应式的、可更改的 ref 对象&#xff0c;此对象只有一个指向其内部值的属性 .value。 作用&#xff1a;创建一个响应式变量&#xff0c;使得某个变量在发生改变时可以同步发生在页面上。 模板语句中使用这个变量时…

模拟实现list

目录 list的实现结构节点的实现迭代器的实现第一个模板参数T第二个模板参数Ref第三个模板参数Ptr 实现list中的接口函数插入和删除赋值重载和拷贝构造析构函数 总结 list的实现结构 STL库中的list的结构是双向循环链表&#xff0c;所以我们这里也实现一个双向循环链表 我们这…

万里路,咫尺间:汽车与芯片的智能之遇

目前阶段&#xff0c;汽车产业有两个最闪耀的关键词&#xff0c;就是智能与低碳。 在践行双碳目标与产业智能化的大背景下&#xff0c;汽车已经成为了能源技术、交通技术、先进制造以及通信、数字化、智能化技术的融合体。汽车的产品形态与产业生态都在发生着前所未有的巨大变革…