【问题标题】:What is the best practice to save a quiz answers with different data types in MYSQL?在 MYSQL 中保存具有不同数据类型的测验答案的最佳做法是什么?
【发布时间】:2020-02-24 00:56:06
【问题描述】:

更新:第 1 部分 - 开始:

我们想要获取我们的客户数据,但是是哪种数据?他们没有指定!实际上我们想要一个动态的应用程序,管理员可以定义新问题(他可以设置答案的数据类型、长度和其他规则),他还可以停用旧问题!

(我不想使用 EAV 架构设计,但我找不到替代方式)

更新:第 1 部分 - 结束:

所以我决定创建一个测验应用程序,管理员可以定义他们的答案数据类型可以不同的问题!

示例:

问题 ID 1:你叫什么名字? 回答:约翰(varchar)

问题 ID 2:你几岁? 答案:25 (整数)

问题 ID 3:你每小时的工资是多少? 答案:30.65 (十进制)

问题 ID 4:描述一下你自己? 回答:我太客气了……(文字)

更新:第 2 部分 - 开始:

为了保存我想到的答案,有 3 个选择:

更新:第 2 部分 - 结束:

  1. 创建这样的表(EAV 架构设计):
    Table profile_answers
    id int [pk, increment]
    question_id int
    profile_id int
    answer text

如您所见,我已将所有答案保存为 文本! 我知道它有效,但这是最好的方法吗?实际上这个应用程序将有数百万个答案,我们想通过机器分析答案(例如:获取客户平均年龄、结婚客户、薪水 > 30.52 的客户等) 所以我想要以最佳性能的方式实施!

  1. 创建这样的表(EAV 架构设计):
Table answers
id int [pk, increment]
question_id int
profile_id int
integer_value int
decimal_value decimal
varchar_value varchar
boolean_value boolean
longtext_value longtext

现在我可以将数据保存在正确的字段中,并将 NULL 放入其他字段(答案的数据类型将在定义问题时确定)

例如:

question ID 1 : what is you name? answer: John (varchar)

id 1
profile_question_id 1
profile_id 1
integer_value NULL
decimal_value NULL
varchar_value John
boolean_value NULL
longtext_value NULL

 ----------------
question ID 2 : how old are you? answer: 25 (integer)

id 2
profile_question_id 2
profile_id 1
integer_value 25 
decimal_value NULL
varchar_value NULL
boolean_value NULL
longtext_value NULL

 ----------------
question ID 3 : how much is your salary per hour? answer: 30.65 (decimal)

id 3
profile_question_id 3
profile_id 1
integer_value NULL
decimal_value 30.65
varchar_value NULL
boolean_value NULL
longtext_value NULL

更新:第 3 部分 - 开始:

  1. 当管理员添加新问题时,我可以在答案表中添加一列(更改表的架构)like this

更新:第 3 部分 - 结束:

一切都与性能有关!最好的方法是什么? 如果它们都不是最好的并且我应该重新设计数据库正确的数据库设计是什么?

【问题讨论】:

  • 如果分析期间的性能是您的首要任务,则使用变体 2。当然,您可以仅填写答案数据类型字段,或具有兼容数据类型的所有字段(字符串 - 仅文本,日期 - 文本和日期, integer - 在文本中,十进制和整数,...)。
  • @Akina 所以你确认第二种方法是最好的解决方案吗?
  • 如果你专注于你的优先事项,是的。
  • EAV 架构设计是一场噩梦。慰问。

标签: mysql database database-performance entity-attribute-value


【解决方案1】:

非 EAV

数据类型是干什么用的?用户将答案键入为 string;他没有指定数据类型,是吗?这避免了 EAV - 只需存储字符串。此时,question 可以只是一个表中的TEXT 列。而answer 是另一个表中的一列,例如您提供的profile_answers

对于诸如“获取客户平均年龄、已婚客户、薪水 > 30.52 的客户”之类的查询,无论是否是 EAV,您都会遇到表扫描。非 EAV 方法会更有效,因为获得价值所需的圈数更少。

你有一个Customers 表;每个客户一行,以生日为一列。平均年龄涉及阅读该专栏并进行一些简单的算术运算。婚姻状况和工资也是如此。

换句话说,用户界面有一个<form> 询问“客户信息”。 (以及其他询问其他内容的表格。)然后,一位客户的答案直接进入专门为客户信息设计的表格中。

可以拥有该表格并且表格是“表格驱动的”。但这是不必要的复杂。如果按照我最初理解的问题,您正在构建一个课堂应用程序,其中涉及包含数百个问题的考试,那可能会导致表格驱动。

EAV

如果您确实坚持使用 5 种数据类型,那么您会遇到以下棘手的问题:DECIMAL 的精度是多少?如果问题是“pi 的值是多少”,那么您如何处理这个品种:3.14、3.1416、3.14159、3.14159265358979、22/7、“大约 3.14”等?只有VARCHAR(...)TEXT 处理这些。

您所说的“(例如:获取客户平均年龄、已婚客户、工资> 30.52 等)”是什么意思?如果您的意思是用户输入了“30.52”,那么将其放入TEXT 列并生成查询就可以了

with salary > "30.52"

也就是说,数值可以作为字符串输入到查询中;数据类型不需要匹配。 (但是“22/7”将被视为 22,“约 3.14”将被视为 0。)

数以百万计的行——您会一次查看所有行吗?常常?如果你有 50 个学生回答 40 个问题,那就是一次完成 2000 个问答;不是数百万。

【讨论】:

  • 数据类型是干什么用的?管理员在创建问题时,可以设置答案的数据类型、长度等规则!因此,当用户回答时,将检查与该问题相关的所有规则并验证答案! “也就是说,数值可以作为字符串输入到查询中”我知道,但关键是我处理大量行和查询时的性能!
  • "如果你有 50 个学生回答 40 个问题":他们不是学生!他们是我们的客户,他们有超过 100000 个测验,每个人每天有 40 多个问题! :) 它会增加!我们想在他们完成测验时分析他们每个人!
  • 我已经更新了问题
  • @soheilyo - 我把第一段改写成几段。
【解决方案2】:

当您在设计时了解架构时,关系数据库最有效。您的要求是仅在运行时才知道架构(对于每个测验)。

EAV 似乎是一种解决方法——它确实允许您存储数据。但是很快查询它变得几乎不可能 - 想象一下试图找到所有 32 岁以上、收入在 20 到 30K 之间并且正在结婚的客户。使用适当的数据类型会有所帮助,但您的查询将很快变得极其复杂,而且无法优化。

好消息是大多数数据库引擎都支持 XML 或 JSON 文档,查询性能非常好。例如,MySQL 的 JSON 做得很好。

我将使用主要概念作为关系实体(客户、测验、问题)对我的系统进行建模,并将答案以 JSON 格式存储在 MySQL 中。

【讨论】:

【解决方案3】:

拥有 NULL 值绝不是一个好习惯。在第 2 点中,您有 8 列,其中 4 列为 NULL。为每个答案的数据类型添加列是不好的做法。

拥有一个字符串数据类型的列是解决为每个答案的数据类型添加可空列的解决方法。

您将通过 2. 方法获得更好的性能。 如果您需要更好的性能,您可以为每种答案数据类型使用单独的表。

我有过测验和表格方面的经验。我们需要有条件显示页面的规则,问题......我们讨论了很多,我很难说服团队我们应该切换到 JSON 或类似的东西(NOSQL)。关系数据库并非旨在解决此类问题。

正确的方法是将测验问题和答案与统计数据分开。 你应该把问题、答案、规则之类的东西移到 NOSQL 上。答案和问题的统计信息应该在关系数据库中。

【讨论】:

  • 这句话“答案和问题统计应该在关系数据库中”与“你应该将问题、答案、规则和诸如此类的东西移动到 NOSQL”没有对比?
  • 拥有 NULL 值绝不是一个好习惯。这个说法值得怀疑。
  • 关于对比:我的意思是测验定义、问题定义、答案的值应该在 NOSQL 中。统计,(例如:获取客户平均年龄、结婚客户、薪水> 30.52 的客户等)应该在SQL 中。请注意,您需要有逻辑来从 SQL 的问题/答案中提取统计信息。
  • 关于空值:更准确地说,只有在超过 90% 的行具有该列的值的情况下,才可以使用空列。其他情况:移动到新表。
  • NULL 节省空间。
【解决方案4】:

根据关于String Type Storage Requirements 的官方MySQL 文档,TEXT 数据类型需要L + 2 个字节,其中L 是字符串的长度。因此,如果给出的答案是数值 100,它将使用 5 个字节。

然而,根据关于 Numeric Type Storage Requirements 的文档条目,相同的数值 (100) 在使用 TINYINT 数据类型(最大 255)时只需要一个字节,而在使用 SMALLINT 类型(最大 65535)时只需要 2 个字节

因此,如果您要在表中存储大量行,则在保存为 TEXT 时,对于小于 256 的值,存储量最多可增加 5 倍。

对于更高的值,存储差异会变得更大:

60000 作为 TEXT = 7 个字节 60000 作为 SMALLINT = 2 个字节

我建议您重新构建数据模型,以便以正确的类型存储数据。这也将确保索引更高效,字符串/数字函数正常工作以及应用程序级抽象层组件,例如 PHP 中的Doctrine, will map to the correct language type 等。

Here is a good article关于优化 MySQL 数据模型设计。

【讨论】:

  • 你推荐使用变体 2 吗?还是两者都不够好?
  • 您应该真正评估您将存储的数据类型,例如,如果数据是数字的并且总是小于 256,那么使用仅使用 1 个字节的 TINYINT 是有意义的。你可以在这里看到完整的表格:dev.mysql.com/doc/refman/8.0/en/…
  • 谢谢,但我不知道评估我要存储的数据类型的正确数据库结构是什么!
  • 根据您的两个变体,第二个更好,因为它存储了正确的数据类型。这意味着索引也将具有更高的性能。但是,可能有更好的方法来设计数据结构。这将取决于您稍后在应用程序中的抽象。查看此链接:oreilly.com/library/view/high-performance-mysql/9781449332471/…
  • 我在答案中添加了更多资源供您查看。
猜你喜欢
  • 2020-05-09
  • 1970-01-01
  • 2021-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-15
相关资源
最近更新 更多