在关系数据库(SQLServer)中将枚举值存储为字符串有什么影响
What are the implications of storing enum values as string in relational database(SQLServer)
所以我需要为带有状态字段的产品生命周期设计数据模型。状态可以是:生产、服务、保修等。
经典方法是单独使用 table 状态和状态的外键。但是我正在考虑在此列中只包含字符串值。我理解的含义是:
- 缺乏价值验证 - 这对我来说没问题,因为
应用程序代码控制此值。
- 可能有更多存储空间 - 不确定这是否正确。 SQL服务器是否有一些优化?
- 索引 - 此处不确定。有什么问题吗?
- 性能 - 此处不确定。是否存在巨大的性能差异?
主要好处是:
- Readable/Semantics - 无论 table 中的数据是什么
立即可读。
- 更易于使用 - 向枚举添加新值只是在应用程序级别添加枚举。无需在 ORM 中进行繁琐的配置。
我错过了什么吗?我概述的含义是真实的吗?你的建议?
我假设 Status 值的数量是有限的,因此可以使用 CHECK 约束在 DB 中进行验证。外键约束的作用几乎相同,但使用引用的 table 中的值并锁定它们。
改变TABLE 产品
添加约束 CH_PRODUCTS_STATUS 检查
(
状态 = 'Production' 或状态 = 'Service'
)
去
是的。无论如何,更多的存储空间。 Int 值将占用 4 个字节,而文本值 'Services' 在 varchar 数据类型中将占用 8 个字节,在 nvarchar 中占用 16 个字节。 Enterprise Edition 中有一个页面压缩选项(在 Standard 中也自 SQL Server 2016 SP1 起)https://msdn.microsoft.com/en-us/library/cc280464.aspx,正如你所见,据我了解这种压缩算法,int 和 varchar/nvarchar 压缩将是 header 大小,因此如果您的 Status 值有一个小的有限选项,压缩会给您相当的结果。我创建了两个结构相同但状态列类型不同的 tables(聚簇索引),并在每个列中插入 100K 行。
创建TABLE 产品
(
Id int 身份主键,
产品 varchar(200) NOT NULL,
状态 varchar(50) NOT NULL,
日期 datetime NOT NULL
)
去
创建 TABLE 产品2
(
Id int 身份主键,
产品 varchar(200) NOT NULL,
状态 int NOT NULL,
日期 datetime NOT NULL
)
去
插入产品值('5656','Service',GETDATE())
去 100000
INSERT INTO Products2 VALUES ('5656', 1, GETDATE())
去 100000
这是他们的一些统计数据:
产品 Table 大小:543 页 (4.2 MB)
产品 2 Table 大小:482 页 (3.7 MB)
Products Compressed(页面压缩)Table 大小:173 页
Products2 Compressed(页面压缩)Table 大小:173 页
注意:本演示中使用的 varchar 数据类型,nvarchar 需要的容量是 varchar 的两倍。
与上一个问题相同的尺寸考虑。这是创建索引的脚本:
创建索引IX_PRODUCTS_STATUS
ON 产品(状态)
去
创建索引IX_PRODUCTS2_STATUS
ON 产品2(状态)
去
产品索引大小:260 页
Products2 索引大小:174 页 (3.7 MB)
产品索引(页面压缩)Table 大小:93 页
Products2 索引(页面压缩)Table 大小:93 页
- 这里是主要的考虑点,为什么我把前两点描述的很详细。在同一个 table 中存储字符串需要更多资源:
- 磁盘space
- 从磁盘读取数据的处理器时间和磁盘 IO
- 用于读取和缓存数据的 RAM
可用 RAM 可以很好地指示在产品 table 中存储状态或将其移至状态 table,因为从 RAM 读取仍然比从磁盘读取快得多。可以使用状态列的值的长度来计算大小差异。如果大小差异很大并且 SQL 服务器将无法在 RAM 中保存活动数据,他将从磁盘读取数据并从 RAM 中清除其他可能重要的数据。在这种情况下,将状态值移动到单独的 table 或启用压缩(如果此选项可用)是有意义的。
但与此同时,如果您将有单独的状态 table,那么创建从产品到状态 table 的外键可能是有意义的。在这种情况下,数据修改操作会因查找状态 table 而变慢一点。此外,您将有 +1 加入,以及物理加入操作的小 CPU 开销(通常可以忽略)。
不幸的是,此解决方案仅可用作您的应用程序的 OLTP 数据库存储。如果您需要使用 table 进行聚合(报告、外部仪表板、日志记录和监控等)或与其他应用程序共享数据(ETL 到 DWH、复制到另一个数据库、缩放节点同步等),您您将面临几个严重的问题:
- 缓慢变化的维度 (https://en.wikipedia.org/wiki/Slowly_changing_dimension)。例如,您想将 "Service" 更改为 "In Progress"。这意味着一些数据仍然有旧名称,新行将采用新名称。在相同的情况下,您需要知道名称已更改,在某些情况下,您需要将它们视为一种状态(用于聚合)。作为折衷方案,您可以添加新列 StatusId,甚至创建“2-Service”、“2-In Progress”等状态。
- Master Data Management (https://en.wikipedia.org/wiki/Master_data_management) 如果外部例程(来自报告工具的报告,excel,tableau,ETL等)想知道列状态的实际字典,您需要创建一个视图,为每个状态提供最后一个值。这样最好在数据库中有 table 和主数据。
有一天您需要将数据从您的应用程序中分离出来,因为您的数据变得比遗留代码更有价值,所以最好提前为那一天做好准备。
所以我需要为带有状态字段的产品生命周期设计数据模型。状态可以是:生产、服务、保修等。
经典方法是单独使用 table 状态和状态的外键。但是我正在考虑在此列中只包含字符串值。我理解的含义是:
- 缺乏价值验证 - 这对我来说没问题,因为 应用程序代码控制此值。
- 可能有更多存储空间 - 不确定这是否正确。 SQL服务器是否有一些优化?
- 索引 - 此处不确定。有什么问题吗?
- 性能 - 此处不确定。是否存在巨大的性能差异?
主要好处是:
- Readable/Semantics - 无论 table 中的数据是什么 立即可读。
- 更易于使用 - 向枚举添加新值只是在应用程序级别添加枚举。无需在 ORM 中进行繁琐的配置。
我错过了什么吗?我概述的含义是真实的吗?你的建议?
我假设 Status 值的数量是有限的,因此可以使用 CHECK 约束在 DB 中进行验证。外键约束的作用几乎相同,但使用引用的 table 中的值并锁定它们。
改变TABLE 产品 添加约束 CH_PRODUCTS_STATUS 检查 ( 状态 = 'Production' 或状态 = 'Service' ) 去
是的。无论如何,更多的存储空间。 Int 值将占用 4 个字节,而文本值 'Services' 在 varchar 数据类型中将占用 8 个字节,在 nvarchar 中占用 16 个字节。 Enterprise Edition 中有一个页面压缩选项(在 Standard 中也自 SQL Server 2016 SP1 起)https://msdn.microsoft.com/en-us/library/cc280464.aspx,正如你所见,据我了解这种压缩算法,int 和 varchar/nvarchar 压缩将是 header 大小,因此如果您的 Status 值有一个小的有限选项,压缩会给您相当的结果。我创建了两个结构相同但状态列类型不同的 tables(聚簇索引),并在每个列中插入 100K 行。
创建TABLE 产品 ( Id int 身份主键, 产品 varchar(200) NOT NULL, 状态 varchar(50) NOT NULL, 日期 datetime NOT NULL ) 去
创建 TABLE 产品2 ( Id int 身份主键, 产品 varchar(200) NOT NULL, 状态 int NOT NULL, 日期 datetime NOT NULL ) 去
插入产品值('5656','Service',GETDATE()) 去 100000
INSERT INTO Products2 VALUES ('5656', 1, GETDATE()) 去 100000
这是他们的一些统计数据:
产品 Table 大小:543 页 (4.2 MB) 产品 2 Table 大小:482 页 (3.7 MB) Products Compressed(页面压缩)Table 大小:173 页 Products2 Compressed(页面压缩)Table 大小:173 页
注意:本演示中使用的 varchar 数据类型,nvarchar 需要的容量是 varchar 的两倍。
与上一个问题相同的尺寸考虑。这是创建索引的脚本:
创建索引IX_PRODUCTS_STATUS ON 产品(状态) 去
创建索引IX_PRODUCTS2_STATUS ON 产品2(状态) 去
产品索引大小:260 页 Products2 索引大小:174 页 (3.7 MB) 产品索引(页面压缩)Table 大小:93 页 Products2 索引(页面压缩)Table 大小:93 页
- 这里是主要的考虑点,为什么我把前两点描述的很详细。在同一个 table 中存储字符串需要更多资源:
- 磁盘space
- 从磁盘读取数据的处理器时间和磁盘 IO
- 用于读取和缓存数据的 RAM
可用 RAM 可以很好地指示在产品 table 中存储状态或将其移至状态 table,因为从 RAM 读取仍然比从磁盘读取快得多。可以使用状态列的值的长度来计算大小差异。如果大小差异很大并且 SQL 服务器将无法在 RAM 中保存活动数据,他将从磁盘读取数据并从 RAM 中清除其他可能重要的数据。在这种情况下,将状态值移动到单独的 table 或启用压缩(如果此选项可用)是有意义的。
但与此同时,如果您将有单独的状态 table,那么创建从产品到状态 table 的外键可能是有意义的。在这种情况下,数据修改操作会因查找状态 table 而变慢一点。此外,您将有 +1 加入,以及物理加入操作的小 CPU 开销(通常可以忽略)。
不幸的是,此解决方案仅可用作您的应用程序的 OLTP 数据库存储。如果您需要使用 table 进行聚合(报告、外部仪表板、日志记录和监控等)或与其他应用程序共享数据(ETL 到 DWH、复制到另一个数据库、缩放节点同步等),您您将面临几个严重的问题:
- 缓慢变化的维度 (https://en.wikipedia.org/wiki/Slowly_changing_dimension)。例如,您想将 "Service" 更改为 "In Progress"。这意味着一些数据仍然有旧名称,新行将采用新名称。在相同的情况下,您需要知道名称已更改,在某些情况下,您需要将它们视为一种状态(用于聚合)。作为折衷方案,您可以添加新列 StatusId,甚至创建“2-Service”、“2-In Progress”等状态。
- Master Data Management (https://en.wikipedia.org/wiki/Master_data_management) 如果外部例程(来自报告工具的报告,excel,tableau,ETL等)想知道列状态的实际字典,您需要创建一个视图,为每个状态提供最后一个值。这样最好在数据库中有 table 和主数据。
有一天您需要将数据从您的应用程序中分离出来,因为您的数据变得比遗留代码更有价值,所以最好提前为那一天做好准备。