哪种数据库设计方法最好,两种选择都有局限性我找不到解决方案
Which database design approach is best, both options have limitations I can't find a solution for
背景:
我正在构建一个在线工具,用于存储和分析表单条目 (MySQL 8.x)。
一个条目包含多个问题(问题的数量可能因表格而异)。
Table 将包含 100 万到 5000 万行,具体取决于以下 2 种方法和条目数。
问题:
卡在如何最好地设计反馈数据的模式 table。
方法一:
1 个完整条目是 1 行(首选)
table 有以下列:
id (primary, ai),
form_id (int),
entry_id(整数),
form_questions(json 列),
form_answers(json 列),
-此 json 列还包含 form_questiontype(有关详细信息,请参阅方法 2),
我不想存储在另一个 table.
中的大约 6 个其他分析相关列
这种方法的好处:
- 我可以很容易地做一个有限制的select。限制 0,100 将给我 100 个完整条目
- 我可以通过查看行的 nr 轻松地看到一个表单有多少条目
- 删除 1 个完整条目更高效
缺点:
- 平面设计。所以对答案进行分析非常慢,因为我不能做全文索引 search/json 列很慢
- 更新 1 个答案可能会很麻烦,但可能是可行的,因为它是一个 json 列,可以在其中定位特定键。
方法二:
将每个 answer/question 存储在 1 行(又名反馈数据)中 + 1 行,其中包含一些分析数据(又名 header).
所以如果一个条目有 10 个问题,我们将得到 11 行。
table 有以下列:
id (primary, ai), form_id (int),
entry_id(整数),
form_question (varchar),
form_answer(varchar,全文索引),
form_questiontype (tinyint),
问题类型很重要,它是一个整数,其中包含特定的 nr。代表一个特定的问题类型。
例如,1=输入字段,2=nps 分数,3=评级等
因此,如果我想知道平均 NPS 分数,我可以在 where 子句中添加 form_questiontype,这样我就可以避免完整的 table 扫描。
这种方法的好处:
- 我可以使用全文索引选项更轻松地执行高效的索引查询
- 我可以很容易地update/delete一个具体的答案
缺点:
- 将生成比接近 1 多得多的行。
- 选择有限制的数据将 return 部分条目。
- 删除一个条目意味着删除 10 多行而不是 1 行。当删除 10.000 个条目时,这将增加查询时间。
最大的缺点是,如果我做一个限制为 0,100 的 select,我不会得到 100 个条目,我得到大约 10 个条目并且一些答案可能不包括在内,因为限制是基于行,而不是 100 个完整条目。
我可以定位 header 行,这将使我有可能进行限制(因为每个条目 1 header 行)。但是这里的问题是我必须在 PHP while 循环中进行另一个查询才能获得实际的反馈数据。
希望能帮到你。
I can target the header row, which will give me the possibility to do a limit (because 1 header row per entry). But the problem here is that I have to do another query in a PHP while loop to get the actual feedback data.
那就不要做 N+1 查询。做两个查询。
header_ids = select id from headers limit 10
feedback_rows = select * from feedback where header_id in header_ids
然后在应用程序中群feedback_rows。
这在很大程度上取决于您的预期用途以及领域模型的完整性。
一般来说,如果您不能提前对问题域建模(例如,您不确定要问什么类型的问题),那么在数据库中将数据存储为 JSON 会很好在您的调查中询问),或者该领域是否没有用关系术语巧妙地建模。
如果您可以合理地确定您在开发时了解领域模型,并且您的领域很适合关系模型,那么使用关系模型是好的。
您担心“我需要查询多少行”或“我可能需要挖掘 10K 行”在很大程度上是无关紧要的 - 具有适当索引的数据模型可以轻松处理这些问题而不会出现明显的性能问题。
假设您想要对调查进行推理(“有多少调查对每个问题都有答案?”,“有多少受访者对问题 6 的评分为 6/10 或更高?”),我会说 JSON 选项非常不合适。
您的模型可能是这样的:
Survey
------
Survey_id (PK)
name
....
Survey_question
--------------
Survey_question_id (PK)
survey_id (fk)
Title
question_type
Survey_response
-----------
survey_response_id (pk)
survey_id (fk)
user_id (fk)
date
...
survey_response_answer
------------
survey_response_answer_id (pk)
survey_response_id (fk)
Survey_question_id (fk)
answer *
- 为答案建模很棘手。据推测,您有几种答案类型 - 数字和文本,也许是日期。我会通过在“survey_response_answer”中为每种类型的响应设置一列来保持简单(因此“answer_numerical”、“answer_text”)。
要获取单个调查的所有答案,您有两种选择:两种查询(一种用于获取 Survey_response
ID,一种用于获取匹配的 survey_response_answer
条目。这样可以避免检索来自 Survey_response table.
的大量重复数据
另一种选择是通过几个连接在单个查询中完成:
select *
from Survey_response sr
inner join survey_response_answer sra
on sr.survey_response_id = sra.survey_response__id
“连接”方法会更快(在所有条件相同的情况下,一个连接查询比 1 个 header 数据查询和 n 个详细数据查询快 n 倍),但您可能需要 trim 删除不需要的 header 数据。
类似下面的草图似乎是合适的:
A table 表格:
CREATE TABLE form
(id integer AUTO INCREMENT,
...
PRIMARY KEY (id));
A table 问题:
CREATE TABLE question
(id integer AUTO INCREMENT,
form integer,
...
PRIMARY KEY (id),
FOREIGN KEY (form)
REFERENCES form
(id)
ON DELETE CASCADE);
- 表单 table 的外键决定问题属于哪个表单。
A table 会话:
CREATE TABLE session
(id integer AUTO INCREMENT,
...
PRIMARY KEY (id));
- 您的元数据在此处。
一个table的答案:
CREATE TABLE answer
(id integer AUTO INCREMENT,
question integer,
session integer,
...
PRIMARY KEY (id),
FOREIGN KEY (question)
REFERENCES question
(id)
ON DELETE CASCADE,
FOREIGN KEY (session)
REFERENCES session
(id)
ON DELETE CASCADE);
- 问题 table 的外键决定回答了哪个问题。
- 会话 table 的外键决定了在哪个会话中回答了问题。
要解决多个租户,您可以为每个租户使用一个 schema/database(可能 cleaner/easier 到 isolate/secure)或者在每个 table 中将租户的 ID 作为外国租户的钥匙 table.
背景:
我正在构建一个在线工具,用于存储和分析表单条目 (MySQL 8.x)。
一个条目包含多个问题(问题的数量可能因表格而异)。
Table 将包含 100 万到 5000 万行,具体取决于以下 2 种方法和条目数。
问题:
卡在如何最好地设计反馈数据的模式 table。
方法一:
1 个完整条目是 1 行(首选)
table 有以下列:
id (primary, ai),
form_id (int),
entry_id(整数),
form_questions(json 列),
form_answers(json 列),
-此 json 列还包含 form_questiontype(有关详细信息,请参阅方法 2),
我不想存储在另一个 table.
这种方法的好处:
- 我可以很容易地做一个有限制的select。限制 0,100 将给我 100 个完整条目
- 我可以通过查看行的 nr 轻松地看到一个表单有多少条目
- 删除 1 个完整条目更高效
缺点:
- 平面设计。所以对答案进行分析非常慢,因为我不能做全文索引 search/json 列很慢
- 更新 1 个答案可能会很麻烦,但可能是可行的,因为它是一个 json 列,可以在其中定位特定键。
方法二:
将每个 answer/question 存储在 1 行(又名反馈数据)中 + 1 行,其中包含一些分析数据(又名 header).
所以如果一个条目有 10 个问题,我们将得到 11 行。
table 有以下列:
id (primary, ai), form_id (int),
entry_id(整数),
form_question (varchar),
form_answer(varchar,全文索引),
form_questiontype (tinyint),
问题类型很重要,它是一个整数,其中包含特定的 nr。代表一个特定的问题类型。
例如,1=输入字段,2=nps 分数,3=评级等
因此,如果我想知道平均 NPS 分数,我可以在 where 子句中添加 form_questiontype,这样我就可以避免完整的 table 扫描。
这种方法的好处:
- 我可以使用全文索引选项更轻松地执行高效的索引查询
- 我可以很容易地update/delete一个具体的答案
缺点:
- 将生成比接近 1 多得多的行。
- 选择有限制的数据将 return 部分条目。
- 删除一个条目意味着删除 10 多行而不是 1 行。当删除 10.000 个条目时,这将增加查询时间。
最大的缺点是,如果我做一个限制为 0,100 的 select,我不会得到 100 个条目,我得到大约 10 个条目并且一些答案可能不包括在内,因为限制是基于行,而不是 100 个完整条目。
我可以定位 header 行,这将使我有可能进行限制(因为每个条目 1 header 行)。但是这里的问题是我必须在 PHP while 循环中进行另一个查询才能获得实际的反馈数据。
希望能帮到你。
I can target the header row, which will give me the possibility to do a limit (because 1 header row per entry). But the problem here is that I have to do another query in a PHP while loop to get the actual feedback data.
那就不要做 N+1 查询。做两个查询。
header_ids = select id from headers limit 10
feedback_rows = select * from feedback where header_id in header_ids
然后在应用程序中群feedback_rows。
这在很大程度上取决于您的预期用途以及领域模型的完整性。
一般来说,如果您不能提前对问题域建模(例如,您不确定要问什么类型的问题),那么在数据库中将数据存储为 JSON 会很好在您的调查中询问),或者该领域是否没有用关系术语巧妙地建模。
如果您可以合理地确定您在开发时了解领域模型,并且您的领域很适合关系模型,那么使用关系模型是好的。
您担心“我需要查询多少行”或“我可能需要挖掘 10K 行”在很大程度上是无关紧要的 - 具有适当索引的数据模型可以轻松处理这些问题而不会出现明显的性能问题。
假设您想要对调查进行推理(“有多少调查对每个问题都有答案?”,“有多少受访者对问题 6 的评分为 6/10 或更高?”),我会说 JSON 选项非常不合适。
您的模型可能是这样的:
Survey
------
Survey_id (PK)
name
....
Survey_question
--------------
Survey_question_id (PK)
survey_id (fk)
Title
question_type
Survey_response
-----------
survey_response_id (pk)
survey_id (fk)
user_id (fk)
date
...
survey_response_answer
------------
survey_response_answer_id (pk)
survey_response_id (fk)
Survey_question_id (fk)
answer *
- 为答案建模很棘手。据推测,您有几种答案类型 - 数字和文本,也许是日期。我会通过在“survey_response_answer”中为每种类型的响应设置一列来保持简单(因此“answer_numerical”、“answer_text”)。
要获取单个调查的所有答案,您有两种选择:两种查询(一种用于获取 Survey_response
ID,一种用于获取匹配的 survey_response_answer
条目。这样可以避免检索来自 Survey_response table.
另一种选择是通过几个连接在单个查询中完成:
select *
from Survey_response sr
inner join survey_response_answer sra
on sr.survey_response_id = sra.survey_response__id
“连接”方法会更快(在所有条件相同的情况下,一个连接查询比 1 个 header 数据查询和 n 个详细数据查询快 n 倍),但您可能需要 trim 删除不需要的 header 数据。
类似下面的草图似乎是合适的:
A table 表格:
CREATE TABLE form
(id integer AUTO INCREMENT,
...
PRIMARY KEY (id));
A table 问题:
CREATE TABLE question
(id integer AUTO INCREMENT,
form integer,
...
PRIMARY KEY (id),
FOREIGN KEY (form)
REFERENCES form
(id)
ON DELETE CASCADE);
- 表单 table 的外键决定问题属于哪个表单。
A table 会话:
CREATE TABLE session
(id integer AUTO INCREMENT,
...
PRIMARY KEY (id));
- 您的元数据在此处。
一个table的答案:
CREATE TABLE answer
(id integer AUTO INCREMENT,
question integer,
session integer,
...
PRIMARY KEY (id),
FOREIGN KEY (question)
REFERENCES question
(id)
ON DELETE CASCADE,
FOREIGN KEY (session)
REFERENCES session
(id)
ON DELETE CASCADE);
- 问题 table 的外键决定回答了哪个问题。
- 会话 table 的外键决定了在哪个会话中回答了问题。
要解决多个租户,您可以为每个租户使用一个 schema/database(可能 cleaner/easier 到 isolate/secure)或者在每个 table 中将租户的 ID 作为外国租户的钥匙 table.