关系数据库中的数据粒度
Data granularity in relational database
我正在休息-api 收到服务器上特定线程应在 24 小时内执行的指令列表,称之为每日计划。在一个时间间隔内执行相同的指令:
[
{
instructionName: string
args : [
string
...
]
startHh : int
startMm : int
endHh : int
endMm : int
}
...
]
args
的内容因instructionName
而异。
时间表应保留在 MySql。每隔 x 秒,线程应该向数据库询问当前指令并做一些工作。
我的问题是我不确定在数据库中存储指令列表的最佳选择是什么。在我看来,我有两个选择:
使用第一种方法,我所要做的就是将 args
连接到单个字符串,然后将 json 直接解析为 DTO 对象并持久化它,我必须小心存储工作线程稍后无法解释的指令名称和参数。工作线程可以很容易地查询指令 table 并得到当前关于时间间隔的指令。
在第二种方法中,我必须首先使用 instructionName 找出 table,查看参数是否对该 table 有效,然后将其插入。工作线程无法以简单的方式获取当前指令,因为指令被分隔在不同的 table 中。当工作线程弄清楚要查询什么 table 时,线程可以确保参数格式正确,因为它们被分成单独的列。
在我的应用程序中将有许多类型的指令,并且在应用程序的生命周期内将不断添加新的指令类型。
这两种方法似乎都有很大的问题,我无法找出适合我的特定用例的最佳方法。我想知道我是否应该为这些类型的数据使用关系数据库。
欢迎任何意见。
任何模型在使用率足够低时都能正常工作。但是,当使用量很大时,DDL(例如 "ALTER TABLE")添加新的论点,即使不是令人望而却步,也会变得非常昂贵。 Table 架构应尽可能少地更改。因此,如果我必须在两者之间做出选择,我更喜欢你的第一个选项。
对我来说,它是否足够好取决于您是否真的想查询参数并可能对高度动态的数据建模,或者您是否只需要一个地方来推送一些文本而不特别关心其中的内容.
例如,如果您希望能够回答诸如 "for which executions of a particular job was the fuelCount
set to 3?" 之类的问题。对于这类问题,您需要能够从本质上非结构化的文本字符串中找到 fuelCount 的存在及其值。它需要不优雅的体操来保持服务器上的分析,但是将每一行拉回到 mysql 客户端来解析参数同样对于除了最小的数据集之外的所有数据集都是站不住脚的。
如果您的数据库版本支持,另一个选项是使用 mysql json features。这使您可以根据需要对参数进行建模,而无需在出现新值时更改 table 格式。但是,您必须足够聪明,以便在更改模型时旧查询不会中断。 json 支持 mysql 意味着能够查询 json 中的数据,而无需检索、解析和聚合数据库客户端中的所有单个记录,因此这是一个非常简洁的功能. Postgres 也有。
例如,您可以在 runtime JSON
列中保存关于要 运行 命令的任意数据,只要您遵守一些简单的规则,您就可以为任何参数,以及(例如)程序可能还需要的环境变量。随着时间的推移,可能会出现其他 运行 时间设置,迫使您向某些作业添加更多参数。 JSON 类型非常适合这个。如果你想查询JSON,你可以。这允许您在数据上施加 some 结构(例如,所有参数都将在顶级字典的 args
键内),而不必预定义每个参数可能会通过。
上面的 link 中有很好的例子,如果您觉得这个想法不错的话。您似乎正在考虑 json 之类的问题,因此这可能是一个简单的过渡。这具有非常网络友好的额外好处,就像您正在构建 REST API 一样,您可能已经计划交换 JSON 了。
如果您对 mysql 的旧版本印象深刻并且无法摆脱它,或者被您的查询所困扰,我仍然建议您坚持使用第一种方法。如果你想更进一步,你可以添加一个 table
CREATE TABLE args ( instruction_id int, argkey varchar, argval varchar)
并使用例如 GROUP_CONCAT 将它们连接在一起,如果 group_concat
的最大长度不是限制因素。否则,您仍然可以在 运行 时连接它们。这对我来说似乎很笨重,但它使可变数据保持在行中,并且它确实允许您在服务器端查询数据。
In my application there are going to be many types of instructions and
new instruction types will be added continuously during the lifetime
of the application.
选择哪种解决方案有点取决于您对很多和连续的定义。数据库非常擅长查询现有数据。它在更改存储数据和添加新数据方面相当不错。更改数据库布局很糟糕。所以你应该尽量避免改变布局。
如果连续改变意味着一天几次,我不建议每个应用程序做一个table。
即便如此,如果很多个应用程序意味着1000个应用程序/参数配置,那么每个应用程序table将导致1000个tables,这相当不受欢迎。
另一方面,如果您选择第一种方法,那么,如您所说,您将必须妥善存储命令及其参数。
如果你有一些存储库模式来处理你的问题的用例,那么你可以让存储库在将参数存储到你的数据库之前检查参数。
void ScheduleBackupTask(TimeSpan startTime, TimeSpan stopTime, ... <backup parameters>)
{
// check the parameter list, to see if they match the parameters of a backup task
// and create the command
var command = CreateBackupCommand(<backup parameters>);
ScheduleCommand(startTime, stopTime, command);
}
void ScheduleCleaningTask(TimeSpan startTime, TimeSpan stopTime, <cleaning parameters>)
{
// check the parameter list, to see if they match the parameters of a clean task
// and create the command
var command = CreateCleanCommand(<cleaning parameters>);
ScheduleCommand(startTime, stopTime, command);
}
void ScheduleCommand(TimeSpan startTime, TimeSpan stopTime, Command command)
{
using (var dbContext = new MyDbContext()
{
Schedule schedule = new Schedule(startTime, stopTime, command);
dbContext.Schedules.Add(shedule);
dbContext.SaveChanges();
}
}
每次您必须支持新命令或必须更改命令的参数时,您都必须创建或更改 Create...Command
函数。只有一处需要检查参数。
即使您选择了第二个解决方案,您也需要一个函数来检查您的参数并将它们按正确的顺序排列。所以你的第二个解决方案不会有帮助。
正在执行命令
显然,使用您的第一种方法查询必须执行的命令更容易、更快捷。获取命令后,包括 commandType
,执行它很容易:
IEnumerable<Command> commandsToExecute = FetchCommandsToExecute(TimeSpan time);
foreach (Command command in commandsToExecute)
{
switch (command.CommandType)
{
case CommandType.Backup:
ExecuteBackup(...);
break;
case CommandType.Clean:
ExecuteClean(...);
break;
}
}
显然,当支持新命令时,您将不得不更改开关。但是,在您的第二个解决方案中,您还必须更改执行功能。
Summarized: if you think of a lot of commands to support, regularly
changing parameters or kind of commands to support, may advice would
be to have one table containing all commands to support. Let your
repository pattern check the parameters before adding / updating /
executing
我正在休息-api 收到服务器上特定线程应在 24 小时内执行的指令列表,称之为每日计划。在一个时间间隔内执行相同的指令:
[
{
instructionName: string
args : [
string
...
]
startHh : int
startMm : int
endHh : int
endMm : int
}
...
]
args
的内容因instructionName
而异。
时间表应保留在 MySql。每隔 x 秒,线程应该向数据库询问当前指令并做一些工作。
我的问题是我不确定在数据库中存储指令列表的最佳选择是什么。在我看来,我有两个选择:
使用第一种方法,我所要做的就是将 args
连接到单个字符串,然后将 json 直接解析为 DTO 对象并持久化它,我必须小心存储工作线程稍后无法解释的指令名称和参数。工作线程可以很容易地查询指令 table 并得到当前关于时间间隔的指令。
在第二种方法中,我必须首先使用 instructionName 找出 table,查看参数是否对该 table 有效,然后将其插入。工作线程无法以简单的方式获取当前指令,因为指令被分隔在不同的 table 中。当工作线程弄清楚要查询什么 table 时,线程可以确保参数格式正确,因为它们被分成单独的列。
在我的应用程序中将有许多类型的指令,并且在应用程序的生命周期内将不断添加新的指令类型。
这两种方法似乎都有很大的问题,我无法找出适合我的特定用例的最佳方法。我想知道我是否应该为这些类型的数据使用关系数据库。
欢迎任何意见。
任何模型在使用率足够低时都能正常工作。但是,当使用量很大时,DDL(例如 "ALTER TABLE")添加新的论点,即使不是令人望而却步,也会变得非常昂贵。 Table 架构应尽可能少地更改。因此,如果我必须在两者之间做出选择,我更喜欢你的第一个选项。
对我来说,它是否足够好取决于您是否真的想查询参数并可能对高度动态的数据建模,或者您是否只需要一个地方来推送一些文本而不特别关心其中的内容.
例如,如果您希望能够回答诸如 "for which executions of a particular job was the fuelCount
set to 3?" 之类的问题。对于这类问题,您需要能够从本质上非结构化的文本字符串中找到 fuelCount 的存在及其值。它需要不优雅的体操来保持服务器上的分析,但是将每一行拉回到 mysql 客户端来解析参数同样对于除了最小的数据集之外的所有数据集都是站不住脚的。
如果您的数据库版本支持,另一个选项是使用 mysql json features。这使您可以根据需要对参数进行建模,而无需在出现新值时更改 table 格式。但是,您必须足够聪明,以便在更改模型时旧查询不会中断。 json 支持 mysql 意味着能够查询 json 中的数据,而无需检索、解析和聚合数据库客户端中的所有单个记录,因此这是一个非常简洁的功能. Postgres 也有。
例如,您可以在 runtime JSON
列中保存关于要 运行 命令的任意数据,只要您遵守一些简单的规则,您就可以为任何参数,以及(例如)程序可能还需要的环境变量。随着时间的推移,可能会出现其他 运行 时间设置,迫使您向某些作业添加更多参数。 JSON 类型非常适合这个。如果你想查询JSON,你可以。这允许您在数据上施加 some 结构(例如,所有参数都将在顶级字典的 args
键内),而不必预定义每个参数可能会通过。
上面的 link 中有很好的例子,如果您觉得这个想法不错的话。您似乎正在考虑 json 之类的问题,因此这可能是一个简单的过渡。这具有非常网络友好的额外好处,就像您正在构建 REST API 一样,您可能已经计划交换 JSON 了。
如果您对 mysql 的旧版本印象深刻并且无法摆脱它,或者被您的查询所困扰,我仍然建议您坚持使用第一种方法。如果你想更进一步,你可以添加一个 table
CREATE TABLE args ( instruction_id int, argkey varchar, argval varchar)
并使用例如 GROUP_CONCAT 将它们连接在一起,如果 group_concat
的最大长度不是限制因素。否则,您仍然可以在 运行 时连接它们。这对我来说似乎很笨重,但它使可变数据保持在行中,并且它确实允许您在服务器端查询数据。
In my application there are going to be many types of instructions and new instruction types will be added continuously during the lifetime of the application.
选择哪种解决方案有点取决于您对很多和连续的定义。数据库非常擅长查询现有数据。它在更改存储数据和添加新数据方面相当不错。更改数据库布局很糟糕。所以你应该尽量避免改变布局。
如果连续改变意味着一天几次,我不建议每个应用程序做一个table。
即便如此,如果很多个应用程序意味着1000个应用程序/参数配置,那么每个应用程序table将导致1000个tables,这相当不受欢迎。
另一方面,如果您选择第一种方法,那么,如您所说,您将必须妥善存储命令及其参数。
如果你有一些存储库模式来处理你的问题的用例,那么你可以让存储库在将参数存储到你的数据库之前检查参数。
void ScheduleBackupTask(TimeSpan startTime, TimeSpan stopTime, ... <backup parameters>)
{
// check the parameter list, to see if they match the parameters of a backup task
// and create the command
var command = CreateBackupCommand(<backup parameters>);
ScheduleCommand(startTime, stopTime, command);
}
void ScheduleCleaningTask(TimeSpan startTime, TimeSpan stopTime, <cleaning parameters>)
{
// check the parameter list, to see if they match the parameters of a clean task
// and create the command
var command = CreateCleanCommand(<cleaning parameters>);
ScheduleCommand(startTime, stopTime, command);
}
void ScheduleCommand(TimeSpan startTime, TimeSpan stopTime, Command command)
{
using (var dbContext = new MyDbContext()
{
Schedule schedule = new Schedule(startTime, stopTime, command);
dbContext.Schedules.Add(shedule);
dbContext.SaveChanges();
}
}
每次您必须支持新命令或必须更改命令的参数时,您都必须创建或更改 Create...Command
函数。只有一处需要检查参数。
即使您选择了第二个解决方案,您也需要一个函数来检查您的参数并将它们按正确的顺序排列。所以你的第二个解决方案不会有帮助。
正在执行命令
显然,使用您的第一种方法查询必须执行的命令更容易、更快捷。获取命令后,包括 commandType
,执行它很容易:
IEnumerable<Command> commandsToExecute = FetchCommandsToExecute(TimeSpan time);
foreach (Command command in commandsToExecute)
{
switch (command.CommandType)
{
case CommandType.Backup:
ExecuteBackup(...);
break;
case CommandType.Clean:
ExecuteClean(...);
break;
}
}
显然,当支持新命令时,您将不得不更改开关。但是,在您的第二个解决方案中,您还必须更改执行功能。
Summarized: if you think of a lot of commands to support, regularly changing parameters or kind of commands to support, may advice would be to have one table containing all commands to support. Let your repository pattern check the parameters before adding / updating / executing