【问题标题】:correct structure (Database) for a mobile application移动应用程序的正确结构(数据库)
【发布时间】:2021-06-11 23:52:50
【问题描述】:

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

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

【问题讨论】:

    标签: mysql sql database-design query-optimization database-normalization


    【解决方案1】:

    选项 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. 包括 MySQL 在内的 DBMS 可以很好地与 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”。

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

    【讨论】:

    • 很好的解释。作为问题作者的附加信息,我还建议在 JOIN 表 user_service 中从 user_id + service_id 创建主键。这是一种很好的做法,因为您将无法插入重复的记录,而且您可能真的不需要有没有角色的 Id 列。
    • 好点@tzu-ax,谢谢。请查看我的编辑。
    • @O.Jones 非常好。我不明白的是。 U说option2更好。然后你说“永远不要在数据列中存储以逗号分隔的事物列表”,但这是选项 2,选项 1 是不同服务的多个列。让我们看看我是否理解你,你会创建 3 个表,1 个用于用户,另一个用于加入用户/服务,另一个用于服务(这将放置用 id 标识的服务,每行一个)。在 users_serv 表中,您可以将用户 ID 作为一行,以及具有用户拥有的不同服务 ID 的几列(但不是在数组中,而是在不同的列中)
    【解决方案2】:

    第三种方式(在太空中最节俭)

    查看SET 数据类型。它允许说明这 6 个服务的组合适用。

    INT UNSIGNED(大小合适)是另一种“设置”的方式。

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

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

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

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

    但是哪个?

    是关于编码的问题吗?好吧,任何方法都将花费一些精力来拆分位/列/字符串以在屏幕上构建按钮。可能与构建屏幕的工作量相似,并且可能少于构建屏幕的工作量。性能也一样。

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

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

    大局

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

    第四条路

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-01
      相关资源
      最近更新 更多