设计表的时候,对于自增列做物理主键使用的一点思考

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 原文:设计表的时候,对于自增列做物理主键使用的一点思考  本文出处:http://www.cnblogs.com/wy123/p/7581380.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误进行修正或补充,无他)     关于自增列 自增列作为数据库的一个特性之一,在MSSQL和MySQL以及Oracle中都被支持。
原文: 设计表的时候,对于自增列做物理主键使用的一点思考

 

本文出处:http://www.cnblogs.com/wy123/p/7581380.html 
(保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误进行修正或补充,无他)

 

 

关于自增列

自增列作为数据库的一个特性之一,在MSSQL和MySQL以及Oracle中都被支持。
之前在网上发现一个类似的问题,是关于MySQL的:“为什么InnoDB表最好要有自增列做主键?”
自增列作为一项特性,(可能)会应用到表的设计方面,不管是在那种数据库平台下。
抛开具体的数据库平台,这个问题可以从更泛的层次去思考,为什么在设计表的时候,需要增加一个自增列做主键?
或者反过来问,设计表的时候,增加一个自增列做主键有什么好处?
多数时候,自增列作为一个冗余字段,在设计表的时候是非必须的,也就是说,一个表中,要不要自增列(甚至要不要自增列作为主键)都是可选的,不是说没有自增列某些表就无法满足业务需求的。
 
当设计表的时候增加一个自增列已经成为一个非强制的要求的时候,做过数据库设计的人对此问题会习以为常。
但是突然有人问上一句“为什么”,还是很难一句话解释清楚的,
比如刚工作不久的人问的问题就很尖锐:“为什么表中要加一个自增列?有什么好处?看起来这个自增列是可有可无的?”
这是一个DB Designer必须要考虑清楚且要随时去回答一些人的质疑的问题。 

 

业务主键与逻辑主键的选择

这之前首先解释两个概念:业务主键与逻辑主键,以设计一个User表为例,有UserId,UserName,CreateDate等等一些属性

业务主键:把具有实际含义的字段作为主键
     通常情况下可以把UserId作为业务主键,主键就暗含了非空+唯一性,一个表中每个UserId是不能为空且唯一的,UserId作为主键并没有什么不妥当的。
     通常来说,业务主键在一个表中的地位很明显,也很直观,比如UserId,订单ID,CustomerId,登录名等等。
逻辑主键:采用与业务无关的唯一性的字段做主键,或称之为“伪主键”
     自增列或者GUID都可以实现所谓的逻辑主键,这里暂且不不讨论GUID(自增与GUID也是一个很大的话题),在User表中增加一个自增列作为主键,因为自增列是也符合主键的特性,也即非空且唯一。
        这个字段的第一个特点是与任何业务逻辑无关,第二个特点是对业务来说是不可见的,
     比如设计一张用户表,在满足存储所有用户信息的条件下,该字段作为一个独立的列存在,而不描述任何业务含义。

以上就是所谓的业务主键和逻辑主键,实话说,仅仅从表面上看,后者并没有什么明显的优势,甚至可以说,如果仅仅就这一张表来说的话,有点冗余的感觉。
但是实际应用中,一个应用是具体一系列复杂的表来支撑的,表与表之间存在着一系列的关联关系,比如1对1或者1对多的关系,此时,自增列的作用就可以提现出来了。

 

业务主键存在的问题

首先说业务主键存在的弊端,业务主键必然要与业务逻辑挂钩,这就意味着业务主键可能是一个或者多个字段的结合,甚至对于每一个业务主键字段都有要求,
比如订单号要求以DH0000000000X开头,或者通常情况下的UserId,比如博客园的登录Id,是介于一定长度之间的字符与数字的组合
这样存在一下几个弊端:
1,当前表的主键与其他表的存在关联关系的时候,必然要存储当前这个表的业务主键信息,甚至不止一个字段,这样无疑增加了表与表之间关联的复杂性与存储空间。
   同理,在表之间join的时候,必然要用基于业务主键的各个条件连接,业务增加SQL的复杂性和降低SQL的执行效率。
2,既然是业务主键,也存在潜在的修改的可能性,有人说主键还会修改,不可能吧,其实这种情况还真不少,比如呢?
   博客园的登录名,作为一个类似于UserId的东西,可以认为是一个业务主键,其特点就是非空+唯一的
   但是这个用户名就是可以修改的,如果其他跟用户信息相关的表存储了用户名,用户名有修改之后,要达到数据的一致性,要修改一系列存储了用户名相关的表。

    再比如,如果用邮箱作为用户名进行注册,比如雅虎邮箱关闭之后,
    注册的各种宝宝账号,如果相关表中直接存储的这个业务主键,也即直接存储的这个邮箱,在需要给用户发送邮件的地方存一个邮箱信息字段,
    那么修改个人邮箱的时候,要涉及多少张表的修改?

  

  另外,在SQLServer中,在Update主键的时候,实际上执行行的是一个delete和insert操作,也就是说会先删除要更新的那条记录,再插入一条记录。
  这也是一个代价相对较大的操作。

3,在SQLServer中,默认情况下主键就是聚集索引,非聚集索引的叶子节点会存储一个指向聚集索引的Key值的,
  一旦一个大的设计了一个较大的主键,那么会导致任何非聚集索引都会包含一个较大的聚集索引key值,这也无疑使得非聚集索引变得膨胀和占用较大的存储空间。
4,不得不考虑的索引碎片问题,同3,默认情况下主键就是聚集索引,如果聚集索引是一个无规律的字段,新填充进来的数据必然会根据排序规则随机存放,
    一旦随机存放,极有可能导致不断的页拆分与伴随着碎片的产生,这对性能来说也不是什么好现象

以上是没有自增列的时候,业务字段作为主键可能潜在的问题。

自增列作为主键解决的问题

如果使用自增列作为主键,均可以避免上述问题,
对于问题1和2,表与表之间的关联,一个自增列就可以完成了,在表之间的join关系的时候,从表仅需存储主表的一个自增字段,就可以将表之间的关联关系串起来
比如博客园的用户名修改,当你修改了用户名,因为其他跟用户相关的表存储的是一个与任何业务没有关联关系的自增字段,随你怎么修改用户名,其他表是根据自增Id来管理的,
自增列的信息永远使用户是不可见,也就意味着永远不会被修改。
对于问题3和4,自增列做主键的时候是唯一的,这就意味着不会存在复合主键列的情况,同时也会消除因为随机字段做聚集索引造成的碎片问题。

自增列主键与聚集索引的补充

  另外一个不可回避的话题就是,聚集索引在查询时候的宝贵性,聚集索引(B树)本身就数据,可以通过聚集索引直接找到数据。
  在进行某些查询或者排序的时候,聚集索引的对于查询性能的好处,一旦将聚集索引建立在一个无业务无关的字段上,聚集索引的价值就白白浪费了。
  在SQLServer数据库中,主键与聚集索引没有必然的关系(MySQL的InnoDB就例外了),聚集索引与其所在字段的是否为空和唯一性也没有必然的关系
  意味着自增列仅仅是为了消除业务主键可能潜在的问题而存在,而不影响聚集索引的自由选择。
  如果设计了自增列,自增列做了主键,主键又是聚集索引,我们知道聚集索引的宝贵性,一张表只能有一个聚集索引。
  在sqlserver中,这一点设计的比较灵活,就是主键不一定非要是聚集索引,可以指定主键是非聚集索引,指定其他键为聚集索引
  同时,对于有唯一性含义的字段,可以指定为NOT NULL+UNIQUE约束,也保证其属性的非空和唯一性。
  对于聚集索引的选择,可以根据具体的业务需要作出合理的选择,比如经常需要按照时间范围查询或者排序,那么就可以将聚集索引建立在CreateDate字段上。

  

  但是在MySQL的InnoDB中,就没有sqlserver上这一点灵活,
  主键必须是聚集索引(即便是没有主键或者唯一约束字段,会自动生成一个主键做聚集索引),这一点没有商量的余地,因此,在MySQL中,“为什么InnoDB表最好要有自增列做主键?”这一点就显得更加有必要。
  

 自增列存在的问题

  以上说明了,自增列作为主键解决了业务字段作为主键存在的问题,但是自增列也并非万能的,
  1,仅仅从存储的角度来看,一个最直观的特点就是冗余存储,也就存储了一个与业务无关的字段,尽管这个字段有其本身存在的诸多的合理性。
  2,并发插入的时候,如果自增列作为聚集索引,可能导致新进来的数据存储在一个逻辑页面上,也就是所谓热点页。
      对于这一点,严格来说,也不仅仅是自增列引起的,而是聚集索引的选择与并发写入时候本身就是一个矛盾体。
     我们总是想让数据按照某种方式来顺序存储,比如按照时间顺序存储,但是在并发存储的时候又会发生热点页的争用问题。
  3,对于分布式数据库,或者多个数据库的数据进行合并作为OLAP分析的时候,每个库中的自增列是独立的,可能会存在冲突的问题
     不过这个问题也不是问题,自增列可以指定自增的开始值和递增规律,来绕开不同数据源中自增列冲突的问题。
     比如A,B两个业务数据库的同样性质的一个表,可以指定在A库中,表上的自增列从1开始,B库中的自增列从100000000开始
     甚至可以指定自增每次增加的值,A库的表的自增列从1开始,每次加2,结果就是1,3,5,7,9,11……;B库的表的自增列从2开始,每次加2,结果就是2,4,6,8,10……
     设置可以根据需求,对自增列的自增特点做的更复杂,这样就会避免在对多个库的数据进行合并分析的时候自增列了的冲突。
   4,类似于自增列的Sequence,Sequence与自增列有部分类似的地方,可以看做是自增列在功能上的扩展。关于Sequence就不多阐述了。
 

自增列使用的范例:
最后看一下参考一下微软给出的AdventureWorks2012示例数据库中的订单表的设计,有兴趣的可以看一下表结构的字段信息。
从业务上看,订单信息,在业务上看,必然有一个能决定订单唯一性的因素:比如CustomerId+OrderDate从理论上说就可以决定唯一的一条数据,
但是他依然使用了一个自增列作为主键,并且在子表,也即订单明细表中使用主表的自增列作为外键,
而对于订单明细表,依然有一个自增列作为主键,因此,可以认为自增列作为逻辑主键,相对还是有一定的优势的。

 

 

总结:

  自增列作为一个冗余存储的字段,在复杂的表结构之中,起着承上启下的关联作用,相当于使用一种轻量级的冗余,来简化业务主键带来的繁复。
  作为一个与任何业务无关的字段,避免业务主键在某些特殊情况下修改带来的一系列问题。

 

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
NoSQL Redis Docker
docker部署redis,清空redis缓存
1、docker ps //查看redis镜像的imgid 2、docker exec -i -t 镜像id或者镜像名称 /bin/bash // 进入容器 3、进入redis-cli目录 执行命令 dbsize && flushall 然后 exit 4、指定key值缓存清理: 1)登录至指定端口的redis服务器 redis-cli -h 127.0.0.1 -p 6379 其中,127.0.0.1可以写成服务器的IP地址,6379为端口号。
6247 0
|
9月前
|
前端开发 API 开发者
一键抠图有多强?19Kstar 的 Rembg 开源神器,5 大实用场景颠覆想象!
Rembg是一款基于Python的开源抠图工具,利用深度学习模型(U-Net/U-2-Net)实现高质量背景移除。它支持命令行、Python API、服务端API及插件等多种形式,适用于电商商品图、社交头像优化、设计项目图像等场景。凭借高精准度、即插即用特性和全面生态,Rembg在GitHub上已获19.1K星,成为开发者社区中的热门工具。其本地部署特性确保数据隐私,适合专业与商业环境使用。项目地址:https://github.com/danielgatis/rembg。
2488 24
|
Linux
CentOS下设置中文编码设置和中文乱码解决
CentOS下设置中文编码设置: 1:安装支持中文: yum -y group install chinese-support 2:修改字符编码配置文件: 默认设置为英文: [root@Tony_ts_tian sysconfig]# pwd /etc/sysconfig [root@Tony_ts_tian sysconfig]# cat i18n LANG="en_US.
6512 0
如何绘制PAD图和N-S图(详细步骤)
如何绘制PAD图和N-S图(详细步骤)
3259 0
|
Linux C语言
成功解决 在Linux CentOS 7 中安装gcc
这篇文章介绍了如何在Linux CentOS 7系统中安装gcc (g++) 8工具集。由于CentOS 7默认的gcc版本是4.8,而这个版本与Qt 5.14、Qt 5.15或更高版本不兼容,可能会导致编译时出现系统头文件错误。文章中提到,即使在项目配置中添加了`CONFIG+=c++11`,如果仍然报错,那么很可能是gcc版本的问题。为了解决这个问题,文章提供了使用CentOS的Software Collections (scl)来安装更新版本的gcc的步骤。
成功解决 在Linux CentOS 7 中安装gcc
|
机器学习/深度学习 数据采集 人工智能
运维新纪元:AIOps引领智能运维变革####
本文探讨了人工智能与运维管理深度融合的前沿趋势——AIOps(Artificial Intelligence for Operations),它通过机器学习、大数据分析等技术手段,为现代IT运维体系带来前所未有的智能化升级。不同于传统依赖人力的运维模式,AIOps能够实现故障预测、自动化修复、性能优化等功能,大幅提升系统稳定性和运营效率。文章将深入分析AIOps的核心价值、关键技术组件、实施路径以及面临的挑战,旨在为读者揭示这一新兴领域如何重塑运维行业的未来。 ####
|
机器学习/深度学习 人工智能 自然语言处理
探索深度学习中的注意力机制:原理、应用与未来趋势
探索深度学习中的注意力机制:原理、应用与未来趋势
852 0
|
Java 数据库连接 mybatis
项目移植到原先mybasis项目里出现BindingException: Invalid bound statement (not found): **selectPage
项目移植到原先mybasis项目里出现BindingException: Invalid bound statement (not found): **selectPage
308 1
|
XML JSON Java
Spring Boot添加消息转换器HttpMessageConverter
spring boot添加消息转换器HttpMessageConverter
3502 0
Spring Boot添加消息转换器HttpMessageConverter
|
SQL 存储 数据库
Hive简介、什么是Hive、为什么使用Hive、Hive的特点、Hive架构图、Hive基本组成、Hive与Hadoop的关系、Hive与传统数据库对比、Hive数据存储(来自学习资料)
1.1 Hive简介 1.1.1   什么是Hive Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能。 1.1.2   为什么使用Hive Ø  直接使用hadoop所面临的问题 人员学习成本太高 项目周期要求太短 MapReduce实现复杂查询逻辑开发难度太大   Ø  为什么要使用Hive 操作接口采用类SQ
28508 0