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

And*_*rew 45 sql database database-design

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

Pet*_*ona 26

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

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


Luc*_*c M 14

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

  • @狼。你可以有版本。有时人们会编码“StartDate”和“EndDate”。想象一下,您有一些设置需要在新年前夜和新年之间更改美国东部标准时间午夜。(又名,新年的第一个第二个)。有人会在午夜坐在那里按下“保存”按钮吗?可能不会。您可以让该行具有开始日期和结束日期,检索代码将根据日期获取设置的“版本”。因此,当新年来临之际,没有人必须亲自到场。 (3认同)

cle*_*tus 13

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

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

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

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

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

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

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

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


Ken*_*itt 13

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

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

如果您手动部署更改您的应用程序数据库,然后是如果你使用的是EAV的设计是非常轻微更易于部署新的配置设置,但真的有什么节省:

INSERT Options ( ConfigurationSetting, Value )
VALUES ( 'NewConfigurationSetting', NewConfigurationSettingValue )
Run Code Online (Sandbox Code Playgroud)

与:

ALTER TABLE Options ADD NewConfigurationSetting some_datatype

UPDATE Options
SET NewConfigurationSetting = NewConfigurationSettingValue
Run Code Online (Sandbox Code Playgroud)


gra*_*der 7

我鄙视将非字符串值放在字符串列中(又名,不正确的数据类型)。(正如@Kenny Evitt 在上面讨论的那样)

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

我实际上不使用钱和小钱。但为了完整起见,我将它们包括在内。请注意,还有一些其他数据类型

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

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

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

它并不完美。Aka,你有很多空元组。但是因为你只抓取一次(和缓存),映射到设置对象(在我的世界中的 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()),    
)
Run Code Online (Sandbox Code Playgroud)

现在,如果这太多了,并且您决定对所有值使用“字符串”,那么这里是一些 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 */
Run Code Online (Sandbox Code Playgroud)

现在,更进一步,您仍然可以创建具有正确数据类型的强类型 dotnet 对象,然后将数据读取器/数据集转换为强对象,如下所示。

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; }
}
Run Code Online (Sandbox Code Playgroud)

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

当没有被否决时,我使用 SettingDataType 和 C# 类,如上所示。


Azi*_*ziz 5

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

但是,这取决于您的属性列表将来是否会更改。

  • 我认为您的意思是2列设计,而不是“ 2行”。 (3认同)