应用程序配置或应用程序选项设置的最佳表设计?

时间:2009-09-07 02:13:02

标签: sql database database-design

我需要在数据库中存储一系列配置值。 我想要存储它们的几种方法是:一个包含2个列(名称,值)的表和每对的行,或者每个配置参数和1行的列表? 第一个我只需要添加另一行来添加配置值,第二行我需要在表中添加一列。我应该考虑哪些问题?一个比另一个更有效吗?

12 个答案:

答案 0 :(得分:25)

对于配置数据,我使用键/值结构,每个配置条目有一行。您可能会一次读取此数据并对其进行缓存,因此性能不是问题。正如您所指出的,每次配置密钥更改时添加列都需要更多维护。

SQL擅长建模和操作任意大量的类似(如果不是相同的)结构化数据。一组配置信息实际上并非如此 - 您有一行数据或者您有多行完全不相关的数据。这表示你只是将它用作数据存储。我说跳过SQL数据模型并简单。

答案 1 :(得分:14)

还有一个考虑因素:每个配置参数都有一列,您可以轻松拥有版本。每行代表一个版本。

答案 2 :(得分:13)

您应该考虑的第一个问题是:停止考虑检索信息的效率。首先,弄清楚如何有效正确对数据建模,然后(并且只有这样)找出如何有效地

因此,这取决于您存储的配置数据的性​​质。如果单独的(名称,值)对基本上不相关,则将其存储为每行一个。如果它们是相关的,那么您可能需要考虑具有多个列的方案。

相关的是什么意思?考虑一些缓存配置。每个缓存都有几个属性:

  • 驱逐政策;
  • 到期时间;
  • 最大尺寸。

假设每个缓存都有一个名称。您可以将此数据存储为三行:

  • <name>_EVICTION
  • <name>_EXPIRY
  • <name>_MAX_SIZE

但是此数据与相关,您可能经常需要一次性检索它们。在这种情况下,拥有一个包含五列的cache_config表是有意义的:id,name,eviction,expiry,max_size。

这就是我所说的相关数据。

答案 3 :(得分:13)

为每个(应用程序)配置设置(或应用程序选项)使用单独的行的一个缺点是,您无法将设置值存储在具有适当数据类型的列中。用户是否可以使用无效的类型输入数据?这与您的申请有关吗?

使用单独列的一个好处是数据库本身的任何代码(例如存储过程,函数等)都可以使用相应数据类型的值,而无需先检查无效值,然后转换为适当的数据类型。

如果您手动将更改部署到应用程序数据库,那么如果您正在使用EAV设计,那么部署新配置设置非常稍微容易一些,但实际上可以节省多少:

INSERT Options ( ConfigurationSetting, Value )
VALUES ( 'NewConfigurationSetting', NewConfigurationSettingValue )

ALTER TABLE Options ADD NewConfigurationSetting some_datatype

UPDATE Options
SET NewConfigurationSetting = NewConfigurationSettingValue

答案 4 :(得分:5)

我认为2列(名称,值)设计要好得多。如你所说,如果你需要添加一个新属性,你需要做的就是“insert”一个新行。在其他设计(单行)中,您需要更改表架构以为新属性添加列。

但是,这取决于您的属性列表将来是否会发生变化。

答案 5 :(得分:4)

我在这里写博客时间moved our AppSettings to a Database Table. 性能不是问题,因为它在应用程序启动时只拉一次并存储在字典中以便于查找。

不确定您的应用程序,但我们这样做的重要原因是,如果您在Dev,Test等中,则无法使用Production值。

答案 6 :(得分:3)

我假设将非字符串值放在字符串列中(也就是错误的数据类型)。 (正如@Kenny Evitt上面讨论的那样)

所以我想出了下面的 替代 ,它是垂直的并处理正确的数据类型。

我实际上并没有使用金钱和小钱。但我把它们包括在内是为了完整性。 注意,还有一些其他数据类型

https://msdn.microsoft.com/en-us/library/ms187752.aspx?f=255&MSPPError=-2147217396

但以下内容涵盖了大部分内容。

老实说,我只使用字符串(varchar(1024)),int,smallint和bit ... 99%的时间。

这不完美。阿卡,你有很多零元组。但是因为你只抓住这些(和缓存),所以映射到设置对象(在我的世界中的c#中)并不困难。

CREATE TABLE [dbo].[SystemSetting](
[SystemSettingId] [int] IDENTITY NOT NULL,

[SettingKeyName] [nvarchar](64) NOT NULL, 
[SettingDataType] [nvarchar](64) NOT NULL, /* store the datatype as string here */

[SettingValueBigInt] bigint NULL, 
[SettingValueNumeric] numeric NULL, 
[SettingValueSmallInt] smallint NULL, 
[SettingValueDecimal] decimal NULL, 
[SettingValueSmallMoney] smallmoney NULL, 
[SettingValueInt] int NULL, 
[SettingValueTinyInt] tinyint NULL, 
[SettingValueMoney] money NULL, 
[SettingValueFloat] float NULL, 
[SettingValueReal] real NULL, 
[SettingValueDate] date NULL, 
[SettingValueDateTimeOffSet] datetimeoffset NULL, 
[SettingValueDateTime2] datetime2 NULL, 
[SettingValueSmallDateTime] smalldatetime NULL, 
[SettingValueDateTime] datetime NULL, 
[SettingValueTime] time NULL, 
[SettingValueVarChar] varchar(1024) NULL, 
[SettingValueChar] char NULL, 

[InsertDate] [datetime] NOT NULL DEFAULT (GETDATE()),               
[InsertedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),       
[LastUpdated] [datetime] NOT NULL DEFAULT (GETDATE()),              
[LastUpdatedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),    

现在,如果这太多了,并且您决定对所有值使用“字符串”,那么这里有一些DDL。

DROP TABLE [dbo].[SystemSetting]
DROP TABLE [dbo].[SystemSettingCategory]

CREATE TABLE [dbo].[SystemSettingCategory] (
    [SystemSettingCategoryId] [int] NOT NULL,
    [SystemSettingCategoryName] [nvarchar](64) NOT NULL, 
    [InsertDate] [datetime] NOT NULL DEFAULT (GETDATE()),               
    [InsertedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),       
    [LastUpdated] [datetime] NOT NULL DEFAULT (GETDATE()),              
    [LastUpdatedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),    
    CONSTRAINT [PK_SystemSettingCategory] PRIMARY KEY CLUSTERED ([SystemSettingCategoryId] ASC),
    CONSTRAINT UQ_SystemSettingCategoryName UNIQUE NONCLUSTERED ([SystemSettingCategoryName])
)   




CREATE TABLE [dbo].[SystemSetting] (
    [SystemSettingId] [int] NOT NULL,
    [SystemSettingCategoryId] INT NOT NULL,     /* FK to [SystemSettingCategory], not shown here */
    [SettingKeyName] [nvarchar](64) NOT NULL, 
    [SettingValue] nvarchar(1024) NULL,
    [InsertDate] [datetime] NOT NULL DEFAULT (GETDATE()),               
    [InsertedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),       
    [LastUpdated] [datetime] NOT NULL DEFAULT (GETDATE()),              
    [LastUpdatedBy] [nvarchar](50) NOT NULL DEFAULT (SUSER_SNAME()),    
    CONSTRAINT [PK_SystemSetting] PRIMARY KEY CLUSTERED ([SystemSettingId] ASC),
    CONSTRAINT FK_SystemSettingCategory_SystemSettingCategoryId foreign key ([SystemSettingCategoryId]) references [SystemSettingCategory] ([SystemSettingCategoryId]),
    CONSTRAINT UQ_SystemSettingCategoryId_SettingKeyName UNIQUE NONCLUSTERED ( [SystemSettingCategoryId] , [SettingKeyName] )
)   



INSERT INTO [dbo].[SystemSettingCategory] ( [SystemSettingCategoryId] , [SystemSettingCategoryName] )
select 101 , 'EmployeeSettings' UNION ALL select 201, 'StopLightSettings'

INSERT INTO [dbo].[SystemSetting] ( [SystemSettingId] , [SystemSettingCategoryId] , [SettingKeyName] , [SettingValue] )
          select 1001 , 101 , 'MininumAgeRequirementMonths' , convert(varchar(16) , (12 * 18))
UNION ALL select 1002 , 101 , 'MininumExperienceMonths' , convert(varchar(8) , 24)
UNION ALL select 2001 , 201 , 'RedLightPosition' , 'top'
UNION ALL select 2002 , 201 , 'YellowLightPosition' , 'middle'
UNION ALL select 2003 , 201 , 'GreenLightPosition' , 'bottom'

/* should fail */
/* start 
INSERT INTO [dbo].[SystemSettingCategory] ( [SystemSettingCategoryId] , [SystemSettingCategoryName] )
select 3333 , 'EmployeeSettings'
INSERT INTO [dbo].[SystemSettingCategory] ( [SystemSettingCategoryId] , [SystemSettingCategoryName] )
select 101 , 'xxxxxxxxxxxxxx'
INSERT INTO [dbo].[SystemSetting] ( [SystemSettingId] , [SystemSettingCategoryId] , [SettingKeyName] , [SettingValue] )
          select 5555 , 101 , 'MininumAgeRequirementMonths' , 555
INSERT INTO [dbo].[SystemSetting] ( [SystemSettingId] , [SystemSettingCategoryId] , [SettingKeyName] , [SettingValue] )
          select 1001 , 101 , 'yyyyyyyyyyyyyy' , 777
INSERT INTO [dbo].[SystemSetting] ( [SystemSettingId] , [SystemSettingCategoryId] , [SettingKeyName] , [SettingValue] )
          select 5555 , 555 , 'Bad FK' , 555
 end */


Select * from [dbo].[SystemSetting] where [SystemSettingCategoryId] = 101 /* employee related */
Select * from [dbo].[SystemSetting] where [SystemSettingCategoryId] = 201 /* StopLightSettings related */

现在,更进一步,您仍然可以使用正确的数据类型创建强类型的dotnet对象,然后将datareader / dataset转换为强对象,如下所示。

public class EmployeeSettings
{
    public Int16 MininumAgeRequirementMonths { get; set; }
    public Int16 MininumExperienceMonths{ get; set; }
}


public class StopLightSettings
{
    public string RedLightPosition { get; set; }
    public string YellowLightPosition { get; set; }
    public string GreenLightPosition { get; set; }
}

您仍然可以使用C#类(或任何语言)........并使用上面的SettingDataType方法。 “映射”代码只需要一些额外的工作。

如果没有outvoted,我会使用如上所示的SettingDataType和C#类。

答案 7 :(得分:2)

您可以使用XML有效地保存配置。某些数据库支持纯XML功能,您可以在其中将值保存为xml数据类型,并且可以在该特定列上运行XQUERY。

创建一个包含两个列名称和配置的表。使用字符串数据类型的名称和具有xml数据类型的配置,因此无需担心新配置参数的插入和删除,您只需在xml中使用新标记。如果数据库不支持XML,那么只需将其保存为字符串,但是以XML格式保存,这样您就可以手动解析该配置或有效地使用某些API。

我认为这是更好的方式,而不是将完整配置存储为字符串。

答案 8 :(得分:1)

CREATE TABLE Configuration (
    Name ...,
    Value ...,
);

最好的方法。向表中添加一个列通常很糟糕,一行表的重点是什么?

不确定这是否适合SQL,但唉......回答问题。

答案 9 :(得分:0)

我使用了两种方法,我更喜欢2列方法。回退到每个配置的新列是您需要更改代码以添加新设置。

我更喜欢每个设置方法使用One列(当我访问该值时)。这是因为更明确地设置了配置设置。但是,这种偏好并没有说明在表中添加新配置的难度。

我会推荐2列方法。然后设置一个访问器函数/ sproc来获取值。

答案 10 :(得分:0)

依赖

如果您的值少于15个,我会为每个值创建一个列。

如果您定期更改设置数,或者您经常不使用所有设置,我会考虑为每个设置添加一行。

除此之外,它可能是一个折腾。取决于您的使用模式。如果您总是需要获取所有设置,那么将它们放在一行可能是最快的。

添加列并不太难,如果你理智地编程,通常不需要更新任何其他代码。

答案 11 :(得分:0)

&#34;最佳&#34;完全取决于上下文 - 这些数据将如何使用?

如果您只需要存储和检索一组配置设置,我首先会对关系数据库的使用提出质疑 - 它对文件系统上的配置文件没有明显的好处。您现在无法轻松地为配置文件使用版本控制,以及管理环境差异(例如&#34; DEV&#34;,&#34; TEST&#34;以及&#34;生产&#34;环境)需要一个GUI来修改数据库(哦,你最初如何连接到数据库?)。

如果您的申请需要&#34;原因&#34;关于整个配置 - 例如如果您有多租户解决方案并且需要基于当前系统动态配置应用程序 - 我建议将配置文件作为文本文档存储在数据库中,其中包含允许应用程序存储/检索的元数据该文件。不同的数据库引擎具有用于存储文本文档的不同解例如,在多租户系统中,您可能有:

ID client_id valid_from     valid_until configuration_file
-------------------------------------------------------
1         1   2016/03/16         NULL      <<DOCUMENT>>

这将允许您检索3月3日之后有效的客户端1的文件,并执行应用程序所需的任何操作。

如果您的应用程序需要推断配置的内容,而不是作为实体的配置,那么您就会遇到其他问题。 &#34;名称/值&#34;您提出的解决方案也称为实体/属性/值(EAV),lots of SO questions讨论了优点和缺点。 TL; DR:使用EAV时,很难将简单的问题转换为SQL。

如果每个配置设置都是具有相应数据类型的列,则查询数据要容易得多。但这确实意味着你最终会得到一个非常宽的&#34;表(大多数应用程序有几十甚至几百个配置值),每次要添加配置设置时,最终都会修改数据库模式,这是不切实际的。

然后,另一种方法是将配置值存储为结构化文档 - 广泛支持XML和JSON。这些格式可以由数据库引擎查询,但不需要固定的模式。