在关系数据库(SQLServer)中将枚举值存储为字符串有什么含义

时间:2017-03-12 08:57:54

标签: sql-server relational-database data-modeling

因此,我需要使用状态字段设计产品生命周期的数据模型。状态可以说是:生产,服务,保修等。

enter image description here

经典方式只是具有单独的表状态,以及状态的外键。但是我想在这个专栏中只有字符串值。我理解的含义是:

  1. 缺乏价值验证 - 在我的情况下这是可以的,因为 应用程序代码控制这个值。
  2. 可能更多的存储 - 不确定这是否正确。 SQL Server是否有一些优化?
  3. 索引 - 这里不确定。有什么问题吗?
  4. 表现 - 这里不确定。是否存在巨大的性能差异?
  5. 主要好处是:

    1. 可读/语义 - 无论表中的数据是什么 立即可读。
    2. 更易于使用 - 为枚举添加新值只是在应用程序级别添加枚举。在ORM中无需繁琐的配置。
    3. 我错过了什么吗?我概述的含义是否真实?你的建议?

2 个答案:

答案 0 :(得分:5)

  1. 我认为有一定数量的Status值,因此可以使用CHECK约束在DB中进行验证。外键约束几乎完全相同,但使用引用表中的值并锁定它们。

    ALTER TABLE产品     添加约束CH_PRODUCTS_STATUS检查     (         状态='生产'或状态='服务'     ) GO

  2. 是。无论如何还要存储更多Int值将占用4个字节,而文本值“Services”将在varchar数据类型中占用8个字节,在nvarchar中占用16个字节。 Enterprise Edition中有一个页面压缩选项(在标准中也是自SQL Server 2016 SP1以来)https://msdn.microsoft.com/en-us/library/cc280464.aspx,正如您所见,据我了解这种压缩算法,int和varchar / nvarchar压缩之间的区别将是标题大小,因此如果Status值有一个小的有限选项,压缩将给你非常相同的结果。我创建了两个表(聚簇索引),它们具有相同的结构,但是对于Status列有不同的类型,并在每个表中插入100K行。

    创建表产品 (     Id int IDENTITY PRIMARY KEY,     Product varchar(200)NOT NULL,     状态varchar(50)NOT NULL,     日期datetime NOT NULL ) GO

    创建表Products2 (     Id int IDENTITY PRIMARY KEY,     Product varchar(200)NOT NULL,     状态int NOT NULL,     日期datetime NOT NULL ) GO

    插入产品价值('5656','服务',GETDATE()) GO 100000

    INSERT INTO Products2 VALUES('5656',1,GETDATE()) GO 100000

  3. 以下是他们的一些统计数据:

    产品表格大小:543页(4.2 MB) 产品2工作台面积:482页(3.7 MB) 压缩产品(页面压缩)表格大小:173页 产品2压缩(页面压缩)表格大小:173页

    注意:本演示中使用的varchar数据类型,nvarchar将需要比varchar多两倍的容量。

    1. 与上一个问题中的大小相同。以下是索引创建的脚本:

      创建索引IX_PRODUCTS_STATUS     ON产品(状态) GO

      创建索引IX_PRODUCTS2_STATUS     ON产品2(状态) GO

    2. 产品索引规模:260页 商品2索引尺寸:174页(3.7 MB) 产品索引(页面压缩)表大小:93页 产品2索引(页面压缩)表大小:93页

      1. 这是主要的考虑因素,为什么我已经用很多细节描述了前两点。在同一个表中存储字符串需要更多资源:
        • 磁盘空间
        • 用于从磁盘读取数据的处理器时间和磁盘IO
        • 用于读取和缓存数据的RAM
      2. 可用RAM可以很好地指示将状态存储在Products表中或将其移动到Statuses表,因为从RAM读取仍然比从磁盘读取快得多。可以使用状态列的值的长度来计算大小差异。如果大小差异很大且SQL Server无法在RAM中保存活动数据,他将从磁盘读取数据并从RAM中清除其他可能很重要的数据。在这种情况下,将状态值移动到单独的表或启用压缩(如果此选项可用)是有意义的。

        但与此同时,如果您将拥有单独的Statuses表,那么从Products to Statuses表创建外键可能是有意义的。在这种情况下,通过查找Statuses表,数据修改操作会稍微缓慢。此外,您将有+1加入,并且物理连接操作的CPU开销很小(并且通常会被忽略)。

答案 1 :(得分:2)

不幸的是,此解决方案仅适用于您的应用程序的OLTP数据库存储。如果您需要使用该表进行聚合(报告,外部仪表板,日志记录和监视等)或与其他应用程序共享数据(ETL到DWH,复制到另一个DB,同步缩放节点等),您将面临几个严重的问题的问题:

  1. 慢慢改变维度https://en.wikipedia.org/wiki/Slowly_changing_dimension)。例如,您想将“服务”更改为“进行中”。这意味着某些数据仍然具有旧名称,新行将采用新名称。在相同的情况下,您需要知道名称已更改,在某些情况下,您需要将它们视为一个状态(对于agregations)。作为妥协,您可以添加新列StatusId,甚至可以创建“2-Service”,“2-In Progress”等状态。
  2. 主数据管理https://en.wikipedia.org/wiki/Master_data_management)如果外部例程(来自报告工具的报告,excel,tableau,ETL等)想知道列状态的实际字典,您需要创建一个视图,为每个状态提供最后一个值。因此,在DB中拥有包含主数据的表会更好。
  3. 有一天,您需要将数据从应用程序中分离出来,因为您的数据比遗留代码更有价值,因此最好事先做好准备。