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

news2024/11/24 1:32:37
聊一个实际问题:淘宝的数据库,主键是如何设计的?
某些错的离谱的答案还在网上年复一年的流传着,甚至还成为了所谓的 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.coloradmin.cn/o/974969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app点击复制指定内容(点击复制)

官方api uni.setClipboardData(OBJECT) uni.setClipboardData({data: 要被复制的内容,success: function () {console.log(success);} });

虹科分享 | MKA:基于先进车载网络安全解决方案的密钥协议

MKA作为MACsec的密钥协议,具有安全、高效、针对性强的特点,为您的汽车ECU通讯创建了一个安全的通信平台,可以助力您的各种汽车创新项目! 虹科方案 | 什么是基于MACsec的汽车MKA 一、MACsec在汽车行业的应用 在以往的文章中&#…

AutoSAR-BSW层概述

一.什么是BSW BSW层全称为Basic Software (基础软件层),顾名思义,该层主要是为应用层提供基础服务。 这里可以看到 BSW 主要提供了:看门狗服务 ,存储服务,通信和诊断服务,OS服务&a…

select 语句执行顺序

sql 样例 select t_students.name as 姓名, sum(case when b.cname语文 then c.sc_val else 0 end) As 语文 ,sum(case when b.cname数学 then c.sc_val else 0 end) As 数学,sum(case when b.cname英语 then c.sc_val else 0 end) As 英语, From t_students a, t_corses b, t…

MIT6.S081实验环境搭建

MIT6.S081 lab 环境搭建 本文参考了MIT的官方指南和知乎文章环境搭建 step1 首先需要一个ubuntu20.04的系统,我使用的是vscode的WSL2连接的ubuntu20.04,使用virtual box建一个ubuntu20.04的虚拟机应该也可以。 可以用 lsb_release -a 查看一下自己ub…

微信小程序开发:一种新型的移动应用程序开发方式

一、引言 随着移动互联网的快速发展,微信小程序作为一种新型的移动应用开发方式,正在受到越来越多的关注。微信小程序是一种基于微信平台的轻量化应用,开发者可以通过微信提供的开发工具和接口,开发出各种具有特定功能的应用程序…

深度解析浏览器工作原理与v8引擎

1.浏览器中js如和被执行的 当我们输入一个域名时会经历dns解析变成一个ip地址,然后浏览器会通过这个ip地址去访问服务器,服务器会放回一个index.html页面,浏览器开始解析这个inde.html文件,当解析到link标签时就开始下载对应的css…

非科班菜鸡算法学习记录 | 代码随想录算法训练营第56天|| 583. 两个字符串的删除操作 72. 编辑距离 编辑距离总结篇

583. 两个字符串的删除操作583. Delete Operation for Two Strings 知识点:动规 状态:看思路自己写 思路: dpij为到i-1和j-1为止的最小操作次数,需要初始化; dp[i][0] 表示i-1要想变成和-1一样的删除次数&#xff0c…

基于CycleGAN的山水风格画迁移

基于CycleGAN的山水风格画迁移 1、简介 1.1 研究背景及意义 绘画是人类重要的一种艺术形式,其中中国的山水画源远流长,具有丰富的美学内涵,沉淀着中国人的情思。游山玩水的大陆文化意识,以山为德、水为性的内在修为意识&#x…

几分钟创建自己的助理

链接: Droxy 上传pdf或者mp4.要求如下: 仅支持PDF或MP4文件。 一次最多可以上传5个文件。 总文件大小应小于1000MB。 视频时长应少于120分钟。 开始使用:

seata1.4.2版本配置总结

文章目录 环境准备:seata-server配置registry.conf创建数据库表 nacos配置client配置seata_orderseata_storageseata_account 注意点 创建项目seata-order-service2001pom.xml文件application.yml 实体类CommonResultOrder DaoOrderDaoOrderMapper.xml config配置类…

华为Mate 60Pro“一机难求”,预计一年出货1200万部,花粉狂欢

根据天风国际分析师郭明錤的报告显示,华为Mate 60 Pro自8月29日上市以来,需求一直保持强劲势头,并且预计下半年出货量将提高约20%,达到550-600万部。与旧款机型相比,华为Mate 60 Pro的需求明显更加强劲。 例如&#xf…

[SSM]MyBatisPlus进阶

三、进阶篇 3.1映射 3.1.1自动映射规则 表名和实体类名映射 -> 表名user 实体类名User字段名和实体类属性名映射 -> 字段名name 实体类属性名name字段名下划线命名方式和实体类属性驼峰命名方式映射 -> 字段名 user_email 实体类属性名 userEmail 开启驼峰命名 m…

靓丽出挑,轻盈舒适:南卡OE Pro运动蓝牙耳机体验

随着科技的不断发展,运动蓝牙耳机在市场上备受瞩目。NANK南卡在运动蓝牙耳机领域深耕细作多年,旗下Runner挂耳骨传导耳机系列细分多款,并且因为优秀的品质、不俗的性价有着亮眼的销量与风评反馈。最近南卡在自家OE的基础上推出了升级Pro款&am…

Apipost:让API研发更高效、更协同

在数字化时代,API(应用程序接口)已经成为数字生态系统中的重要组成部分。API不仅使得不同应用程序能够相互通信,还成为了企业间进行数据交换和业务整合的关键手段。在这个趋势下,API研发工具市场也日益繁荣&#xff0c…

【Linux】Ubuntu20.04版本安装谷歌中文输入法【教程】

【Linux】Ubuntu20.04版本安装谷歌中文输入法【教程】 文章目录 【Linux】Ubuntu20.04版本安装谷歌中文输入法【教程】一、下载fcitx-googlepinyin二、配置Language SupportReference 一、下载fcitx-googlepinyin 使用下面的命令行下载fcitx-googlepinyin sudo apt-get insta…

【TI毫米波雷达笔记】MMwave毫米波雷达API配置及驱动(以IWR6843AOP为例)

【TI毫米波雷达笔记】MMwave毫米波雷达API配置及驱动&#xff08;以IWR6843AOP为例&#xff09; MMWave API&#xff08;位置处于BSS&#xff09;可以被DSS和MSS调用 通过Mailbox进行数据通信 毫米波雷达接口 #include < ti/control/mmwave/mmwave.h> #include <ti/…

Redis基本了解

Redis 基于内存进⾏存储&#xff0c;⽀持 key-value 的存储形式&#xff0c;底层是⽤ C 语⾔编写的。 基于 key-value 形式的数据字典&#xff0c;结构⾮常简单&#xff0c;没有数据表的概念&#xff0c;直接⽤键值对的形式完成数据的管理&#xff0c;Redis ⽀持 5 种数据类型…

运维Shell脚本小试牛刀(六): Shell中的函数|本地变量

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 运维Shell脚本小试…

14.Redis 主从复制

Redis 主从复制 redis 主从复制配置 redis 主从复制启动 redis 主从复制断开 redis 主从复制主从复制构特点主从复制的拓扑结构一主一从⼀主多从树状主从 主从复制原理数据同步psync 运行流程全量复制流程部分复制流程实时复制 关于从节点何时晋升成主节点总结 redis 主从复制 …