移动应用程序的正确结构数据库

问题描述

我们正在和一些朋友一起做一个移动应用程序,但是由于未知,我们在数据库结构方面遇到了问题。我认为这是一个可以帮助很多人的好问题,如果有知识的人可以好好解释一下。该应用程序包括向客户提供各种服务(将来可以添加更多服务)。他们已登录并有权访问我们的服务。起初,我们想到了一个包含所有客户数据和服务的列的表。然后我们看到创建另一个名为“services”的单独表并通过 id 标识用户更有效。现在问题出现在这张桌子上。我们不知道是将所有服务(例如数组)制作成一列还是每个服务制作一列。我拍了一张照片,以便更容易观察我的提议。

enter image description here

问题在于,就性能而言,这些选项中的哪一个(显然可能还有三分之一我们没有考虑过)是最好的。 我认为第二个选项我看到了几个缺陷,但我不确定。在延迟和速度方面,遍历数组(如果添加服务则更多,或者可能因为用户先租用 service2 然后 1 而乱序)比选项 1 中的要高得多。此外,事实是 a用户在服务下,这意味着遍历整个数组,寻找它并消除它。不知道各位高手,你们有什么推荐吗?所有这些都会上传到云端(azure),所以所有的请求都会上传到云端

解决方法

选项 2 比选项 1 好。但是,就尊重而言,它仍然不好。

永远不要在数据列中存储逗号分隔的事物列表。如果你这样做,你会后悔的。 (搜索它们的成本非常高。)

你想要这样的东西。三个表,一个用于用户,另一个用于服务,还有一个所谓的 JOIN 表,用于在两者之间建立多对多关系。

+-----------+    +-------------+     +-----------+
|user       |    |user_service |     |service    |
+-----------+    +-------------+     +-----------+
|user_id    +--->|user_id      |<----+service_id |
|givennamee |    |service_id   |     |name       |
|surname    |    +-------------+     +-----------+
|is_active  |
+-----------+

user_service 中的每一行表示用户有权使用该服务。要授权用户,请插入一行。要撤销授权,请删除该行。

要了解用户是否可以使用服务,请使用此查询。

SELECT user.user_id 
  FROM user
  JOIN user_service USING (user_id)
  JOIN service USING (service_id)
 WHERE user.givenname = 'Bill' AND user.surname='Gates'
   AND service.name = 'CharityNavigator'
   AND user.is_active > 0;

如果您的查询返回 user_id,则所选用户可以使用所选服务。

要获取每个用户的服务列表,请使用此查询。

SELECT user.user_id,user.givenname,user.surname,GROUP_CONCAT(service.name) service_names
  FROM user
  JOIN user_service USING (user_id)
  JOIN service USING (service_id)
 WHERE user.is_active > 0
 GROUP BY user.user_id  

一些解释: 几乎总是最好为诸如服务之类的内容构建带有行的表,而不是列或以逗号分隔的列中的列表。为什么?

  1. 您可以添加新服务 - 随心所欲 - 数年后无需重新编写数据库代码。

  2. DBMS,包括 MySQL,可以很好地处理 JOIN 操作。

  3. 在大多数关系数据库管理系统中,执行 WHERE commalist_column SOMEHOW_CONTAINS (some_id) 的效率低得令人厌恶。执行 WHERE column = some_id 的效率要高得多,因为它可以使用索引。

  4. 列数少的行通常比列数多的行效果更好。

  5. 在生产环境中向数据库添加行比添加列要便宜得多。添加列意味着更改表定义。该操作可能需要停机。

当您将列用于服务等内容时,您正在创建一个封闭的系统。当您使用行时,您的系统是开放式的。

我可以建议您阅读有关 database normalization 的内容吗?不要被所有的 CS 术语吓倒。看看如何规范化各种数据库的一些例子。

也许读过entity-relationship database modeling

编辑 根据评论者的建议,我建议您使 user_service 表的主键包含两列 (user_id,service_id)。我还建议您使用两列 (service_id,user_id) 创建反向索引,以便您的查询可以从服务和用户开始快速查找。您的表定义可能如下所示:

CREATE TABLE user (
    user_id INT UNSIGNED NOT NULL AUTO_INCREMENT,givenname VARCHAR(50) NULL DEFAULT NULL,surname VARCHAR(50) NULL DEFAULT NULL,is_active TINYINT NOT NULL DEFAULT '1',PRIMARY KEY (user_id)
)
COLLATE='utf8mb4_general_ci';

CREATE TABLE service (
    service_id INT UNSIGNED NOT NULL AUTO_INCREMENT,name VARCHAR(50) NULL DEFAULT NULL,PRIMARY KEY (service_id)
)
COLLATE='utf8mb4_general_ci';

CREATE TABLE user_service (
    user_id INT UNSIGNED NOT NULL,service_id INT UNSIGNED NOT NULL,PRIMARY KEY (user_id,service_id),INDEX reverse_index (service_id,user_id),CONSTRAINT FK_service 
               FOREIGN KEY (service_id)
               REFERENCES service (service_id)
               ON UPDATE RESTRICT ON DELETE RESTRICT,CONSTRAINT FK_user 
               FOREIGN KEY (user_id)
               REFERENCES user (user_id)
               ON UPDATE RESTRICT ON DELETE RESTRICT
);

如果您尝试使用此主键为用户插入重复的服务授权,dbms 会拒绝它。

请务必在这些表中使用相同的“INT UNSIGNED NOT NULLdata type foruser_idandservice_id”。

这是一种非常常见的数据库设计模式:它是在两个不同表的行之间创建多对多关系的规范方式。

,

第三种方式(最节省空间)

请参阅 SET 数据类型。它允许说明这 6 个服务的哪个组合适用。

INT UNSIGNED(大小合适)是另一种拥有“集合”的方式。

SET 或 TINYINT 只需要 1 个字节就可以表示最多 8 个项目。

您的 6 列选择需要 6 个字节。

“{serv1,... }”可能是一个 VARCHAR,平均为 10-20 个字节。

所以,我的建议显然是为了节省空间。但也许这并不重要?你有数百万或行吗?你有更多的 tnan 64“服务”吗? (SET 和 BIGINT UNSIGNED 的限制为 64。)

但是哪个?

是关于编码的问题吗?好吧,任何方法都需要付出一些努力才能将位/列/字符串分开以在屏幕上构建按钮。可能需要类似的工作量,而且可能比构建屏幕的工作量少。性能同上。

我强烈建议您选择两种解决方案并同时实施。你会发现

  • 它们在性能、代码数量等方面的相似程度。
  • 这个问题多么微不足道。
  • 您对数据库了解了多少额外的知识。
  • “尝试”和“放弃”另一种做某事的方式是多么容易。
  • 延迟、性能等差异如何微不足道。 (这就是我们真正为您解答的问题。)

大局

您已经指出了此数据结构的一种用途。我担心这种数据结构有或将会有其他用途。其他因素才是决定哪种方法最好的真正决定因素。 (到时候,你就可以愉快地复活被丢弃的版本了!)

第四种方式

JSON。但它会比你的 VARCHAR 方式更冗长(占用更多空间)。它可能更容易使用,也可能不会更容易 - 这取决于其余要求