我应该devise一个主键为varchar或int的表吗?
我知道这是主观的,但我想知道人们的意见,并希望在deviseSQL Server表结构时可以应用一些最佳实践。
我个人觉得,在一个固定的(最大)长度的varchar上键入一个表是一个禁止的,因为这意味着必须在任何其他使用这个作为外键的表上传播相同的固定长度。 使用一个int
,将避免必须在板上应用相同的长度,这将导致人为错误,即1个表具有varchar (10)
,另一个varchar (20)
。
这听起来像是一个噩梦初步设置,再加上意味着未来维护表是繁琐的。 例如,键入的varchar列突然变成了12个字符而不是10个。现在必须去更新所有其他表,这可能是一个非常艰巨的任务。
我错了吗? 我在这里错过了什么吗? 我想知道别人怎么看这个,如果用主键坚持int是避免维护噩梦的最好方法。
我肯定会build议在每个表中使用INT NOT NULL IDENTITY(1,1)
字段作为主键。
通过一个IDENTITY字段,你可以让数据库处理所有的细节,确保它是真正唯一的,而INT数据types只有4个字节,并且是固定的,所以它更容易,更适合用于主要(和集群)在你的桌子上的钥匙。
你是对的 – INT是一个INT是一个INT – 它不会改变任何东西的大小,所以你不需要重新创build和/或更新你的外键关系。
使用VARCHAR(10)或(20)只占用太多的空间–10或20个字节而不是4个,很多人不知道 – 每个索引项上的每个索引项都会重复使用集群键值表上的单一非聚集索引,所以可能会浪费很多空间(不仅仅是在磁盘上 – 这很便宜,而且在SQL Server的主内存中)。 另外,由于它是可变的(可能是4,可能是20个字符),SQL服务器很难正确维护一个好的索引结构。
渣子
通常select主键时,也可以select聚簇键。 他们两个经常困惑,但你必须了解其中的差异。
主键是逻辑业务元素。 主键被你的应用程序用来识别一个实体,关于主键的讨论在很大程度上是使用自然键或代理键 。 链接进入更多的细节,但基本的想法是,自然键是从现有的实体属性,如ssn
或phone number
派生,而代理键没有任何意义的商业实体,如id
或rowid
,他们是通常是typesIDENTITY
或某种uuid。 我个人的观点是,代理键优于自然键,select应该是本地唯一应用程序的身份值,任何分布式数据的指导。 主键在实体的生命周期中永远不会改变。
聚簇键是定义表中行的物理存储的关键。 大多数情况下,它们与主键(逻辑实体标识符)重叠,但实际上并不强制也不要求。 当两者不同时,表示在实现主键的表上有一个非聚集唯一索引。 在行的生命周期中,集群键值可能会发生实际变化,导致该行在表中物理移动到新位置。 如果必须将主键与群集密钥分开(有时是这样做的),那么select一个好的群集键比select主键困难得多。 有两个主要因素可以推动您的群集密钥devise:
- stream行的数据访问模式 。
- 存储的考虑 。
数据访问模式 。 通过这个我了解表格被查询和更新的方式。 请记住,聚簇键确定表中行的实际顺序。 对于某些访问模式,有些布局会在查询速度或更新一致性方面在世界上有所不同:
-
电stream与存档数据。 在许多应用程序中,属于当前月份的数据经常被访问,而过去的数据很less被访问。 在这种情况下,表格devise使用按交易date划分表格 ,通常使用滑动窗口algorithm。 当前的月份分区保存在位于热点快的磁盘上的文件组中,归档的旧数据将移动到更便宜但速度更慢的存储上的文件组。 很明显,在这种情况下,聚集键(date)不是主键(事务ID)。 两者之间的分离是由规模需求驱动的,因为查询优化器将能够检测到查询只对当前分区感兴趣,甚至没有查看历史数据。
-
先进先出队列式处理。 在这种情况下,表有两个热点:发生插入的尾部(入队)和发生删除的头部(出队)。 群集密钥必须考虑到这一点,并组织该表,以便在磁盘上物理分离尾部和头部位置,以便在入队和出队之间进行协调,例如。 通过使用排队命令键。 在纯队列中,这个聚簇键是唯一的键,因为表中没有主键(它包含消息 ,而不是实体 )。 但是大多数情况下,队列并不是纯粹的,它也可以作为实体的存储空间, 队列和表之间的界线是模糊的。 在这种情况下,还有一个主键,它不能是聚簇键:实体可能会被重新排列,从而改变排列顺序的聚簇键值,但不能改变主键值。 没有看到分离是用户表支持队列如此臭名昭着地很难得到正确和充满死锁的主要原因:因为入队和出队发生交错产生表,而不是本地化在队尾和队首。
-
相关的处理。 当应用程序devise良好时,它将在其工作线程之间分配相关项的处理。 例如,处理器被devise为具有8个工作者线程(比如说与服务器上的8个CPU相匹配),以便处理器在他们自己之间分配数据, 工人1只拾取名称为A到E,工作人员2 F到J等的账户。在这种情况下,该表应该实际上通过账户名(或者具有最左边位置的账户名称的第一个字母的组合键)聚类,以便工作人员将他们的查询和更新本地化。 这样的桌子有8个不同的热点,每个工作人员目前专注于这个区域,但重要的是他们不重叠(没有阻塞)。 这种devise在高吞吐量OLTPdevise和TPCC基准负载中普遍存在,这种分区也反映在加载在缓冲池(NUMA locality)中的页面的内存位置,但是我离题了。
存储注意事项 。 聚簇键宽度在表的存储中具有巨大的重新运行。 因为密钥占据了B树的每个非叶页面的空间,所以一个大键将占用更多的空间。 其次,通常更重要的是,聚簇键被每个非被关键的键用作查找关键字,所以每个非关键关键字将必须存储每行的关键关键字的全部宽度。 这就是像varchar(256)这样的大型集群键,并且对于聚簇索引键,GUIDselect不当。
此外,密钥的select会影响聚簇索引碎片,有时会严重影响性能。
这两种力量有时可能是敌对的,数据访问模式需要一定的大型密钥,这会造成存储问题。 在这种情况下当然需要平衡,但是没有神奇的公式。 你测量和testing,以达到最佳位置。
那么我们从这些方面做了什么? 总是从考虑也是entity_id IDENTITY(1,1) NOT NULL
forms的主键的聚集键开始 。 将两者分开,并在适当时组织相应的表(例如按date分区)。
我同意,一般来说,INT(或身份)字段types是大多数“正常”数据库devise中的最佳select:
- 它不需要“algorithm”来生成id / key /值
- 你有快速的(呃)连接,优化器可以在范围内工作很多,比如在引擎盖下
- 你正在遵循一个事实标准
也就是说,你也需要知道你的数据。 如果你打算通过一个有符号的32位整数,你需要考虑无符号。 如果你打算通过这个,也许64位整数是你想要的。 或者,也许你需要一个UUID /哈希来使数据库实例/分片之间的同步更容易。
不幸的是,这取决于和YMMV,但我肯定会使用一个int /身份,除非你有一个很好的理由不要 。
就像你说的,一致性是关键。 我个人使用无符号整数。 除非使用大量数据,否则不会耗尽它们,而且您始终可以知道任何关键列都必须是该types的,而且您无需为单个列寻找正确的值。
基于无数次的这个练习,然后用这个结果来支持这个系统,对INT总是更好的总括陈述有一些警告。 一般来说,除非有理由,否则我会同意的。 然而,在战壕中,这里有一些优点和缺点。
INT
- 除非有充分的理由才使用。
GUID
- 唯一性 – 一个例子就是程序的远程部分之间有单向通信,需要启动的部分不是数据库的一端。 在这种情况下,select一个INT的时候,在远端设置一个Guid是安全的。
- 唯一性 – 一个更为遥远的场景是一个系统,其中多个客户共存在不同的数据库中,并且像使用一套程序的类似用户一样在客户之间进行迁移。 如果该用户注册另一个程序,则可以在那里使用他们的用户logging而不会发生冲突。 另一种情况是,如果客户彼此获得实体。 如果两者都在同一个系统上,他们通常会期望迁移更容易。 本质上,任何客户之间的频繁迁移。
-
很难使用 – 即使是一个有经验的程序员也不会记得一个guid。 在进行故障排除时,必须复制和粘贴查询的标识符,特别是使用远程访问工具进行支持时,通常会感到沮丧。 与SELECT * FROM Xxx WHERE ID ='DF63F4BD-7DC1-4DEB-959B-4D19012A6306'相比,经常引用SELECT * FROM Xxx WHERE ID = 7要容易得多。
-
索引 – 对guid字段使用聚簇索引需要不断重新排列数据页面,并且不像INT或甚至短string那样有效。 它可以杀死性能 – 不要这样做。
CHAR
- 可读性 – 尽pipe传统观点认为数据库中不应该有任何人,系统的现实是人们可以访问组织的人员。 当那些人不懂join语法时,没有许多其他查询,带有整数或指导的规范化表格就不清楚。 使用SOMEstring键的同一个标准化表格可以更方便地进行故障排除。 我倾向于使用这种types的表在安装时提供logging,所以他们不会有所不同。 像主键上的StatusID这样的东西,比起数字来说,当关键字是“closures”还是“待定”时,更能用于支持。 在这些领域使用传统的密钥可以将一个容易解决的问题变成需要开发人员协助的事情。 这样的瓶颈即使是由让有问题的人员访问数据库造成的也是不好的。
- 限制 – 即使你使用string,保持固定的长度,这加快了索引,并添加一个约束或外键,以防止垃圾。 有时使用这个string可以让你删除查找表,并在代码中将select保持为一个简单的Enum – 限制数据进入这个字段仍然很重要。
为了获得最佳性能 ,99.999%的主键应该是单个整数字段。
除非您要求主键在数据库中的多个表或多个数据库之间是唯一的。 我假设你问MS SQL服务器,因为这是如何标记你的问题。 在这种情况下,请考虑使用GUID字段。 虽然比varchar更好,但GUID字段的性能不如整数。
使用INT。 你的观点都是有效的。 我会优先考虑为:
- 轻松使用SQL自动增加能力 – 为什么重新发明轮子?
- 可pipe理性 – 您不希望改变关键字段。
- 性能
- 磁盘空间
1和2需要开发者的时间/精力/努力。 3&4你可以扔硬件在。
如果Joe Celko在这里,他会有一些苛刻的话… 😉
我想指出,INTs作为一个硬性规定并不总是合适的。 假设你有一个汽车卡车等所有types的车辆表。现在说你有VehicleType表。 如果你想得到所有的卡车,你可以这样做(用INT标识种子):
SELECT V.Make, V.Model FROM Vehicle as V INNER JOIN VehicleType as VT ON V.VehicleTypeID = VT.VehicleTypeID WHERE VT.VehicleTypeName = 'Truck'
现在,在VehicleType上使用Varchar PK:
SELECT Make, Model FROM Vehicle WHERE VehicleTypeName = 'Truck'
代码是一点点清洁,你避免了join。 也许join并不是世界末日,但是如果你的工具箱里只有一个工具,那么你错过了一些性能提升和更干净的模式的机会。
只是一个想法。 🙂
虽然一般build议使用INT
,但这取决于您的情况。
如果你关心可维护性,那么其他types也是可行的。 例如,您可以非常有效地使用Guid作为主键。 有没有这样做的原因,但一致性不是其中之一。
但是,是的,除非你有一个很好的理由不要,int是最简单的使用,而最不可能导致你的任何问题。
对于PostgreSQL,我通常使用“Serial”或“BigSerial”数据types来生成主键。 值是自动递增的,我总是发现整数很容易处理。 它们本质上等同于设置为“auto_increment”的MySQL整数字段。
一个人应该认真思考32位范围是否足够用于你正在做的事情。 Twitter的状态ID是32位INT,他们跑出去时遇到了麻烦。
在这种情况下是使用BIGINT还是UUID / GUID是有争议的,我不是一个硬核数据库的人,但是UUID可以存储在一个固定长度的VARCHAR中,而不用担心你需要改变字段的大小。
我们必须记住,表的主键不应该有“业务逻辑”,它应该只是它所属logging的一个身份。 遵循这个简单的规则,int和特别是一个身份int是一个非常好的解决scheme。 通过问关于varchar我猜你的意思是使用“全名”作为“人”表的关键。 但是如果我们想把名字从“George Something”改成“George A. Something”呢? 这个领域是多大? 如果我们改变尺寸,我们也必须改变所有外表的尺寸。 所以我们应该避免关键的逻辑。 有时我们可以使用社交ID(整数值)作为关键,但是我也避免这样做。 现在如果一个项目有扩大的前景,你也应该考虑使用Guid(uniqueidentifier SQLtypes)。
请记住,这是一个相当古老的问题,我仍然想要使用varchar与未来的读者代理键:
- 有几个复制机器的环境
- 在需要被插入的行的ID在实际插入之前是已知的场景(即,客户端分配该ID而不是数据库)