0

我试图找出存储 (var)char 类型的节点之间关系的最佳数据库和表结构。多年前我最后一次使用 MySQL 作为一些简单 PHP 网页的后端,但从未超越。希望有经验的用户可以给我意见。

假设我有一堆名字:

  • 托马斯
  • 菲利克斯
  • 马克
  • 安妮

我现在想存储他们的关系。我的想法是有两个可能看起来像这样的表:

names (id, name)        relationships (id_1, id_2)
0 Thomas                0 1
1 Jane                  0 3
2 Felix                 1 2
3 Marc                  3 4
4 Anne                  ...
...                     

数据范围如下:

  • 表“名称”将包含大约。500 万行。
  • 表“关系”将包含 150-2 亿行。
  • 数据库只能由我在本地访问(服务器和客户端是同一台机器)
  • 我不需要像 Web 服务器那样的响应能力,只需要在我访问它的少数情况下实现高吞吐量(以减少等待时间)

我的问题是:

  • 我记得正确使用 PRIMARY_KEY 很重要。我隐约记得有可能将键分配给两列(即 id_1,在我的情况下为 id_2);这有助于查询我想象的?
  • MySQL 中有没有办法防止在插入期间创建重复关系(例如 0:4 和 4:0)?
  • MySQL 对我来说默认为 InnoDB。这是您为我的场景推荐的数据库吗?

欢迎任何指点。谢谢你。

4

1 回答 1

0

首先,您需要考虑您的关系是否有与之相关的“方向”。例如,关系“is a child of”与其他相同的关系“is a parent of”具有相反的方向;另一方面,“是兄弟姐妹”的关系是无向的(或双向的,取决于一个人的观点)。

您描述的结构非常适合定向关系。

另一方面,双向关系通常最好通过故意执行第二个要点中描述的重复来表示;虽然这会消耗更多的存储空间,但它极大地简化了诸如“查找 X 的所有兄弟姐妹”之类的查询——否则这可能需要合并两个单独的查询:

SELECT id_2 FROM my_table WHERE id_1=X
UNION
SELECT id_1 FROM my_table WHERE id_2=X

因为在结果列上没有索引,所以如果想要对结果做更多​​的事情(例如 sort byid或与names表连接),这些类型的查询可能会非常慢 - 尽管在这种特殊情况下可以执行连接在联合之前,但这只会增加数据操作代码的冗余和复杂性)。

可以使用触发器来确保每当将关系写入(插入、更新或删除)到表示双向关系的表中时,都会自动对反向关系执行相同的操作。

其次,您描述的表示称为“邻接表”,非常简单易懂。但它并不擅长处理通过数据层次结构进行的深度搜索,尤其是在 MySQL 上(与其他一些 RDBMS 不同,它不支持递归函数)。因此找到“X 的所有后代”或“Y 的所有祖先”实际上是相当困难的。其他数据模型,例如“嵌套集”或“传递闭包”对于这些任务要好得多。

有了序言,关于你的问题:

  • 我记得正确使用 PRIMARY_KEY 很重要。我隐约记得有可能将键分配给两列(即 id_1,在我的情况下为 id_2);这有助于查询我想象的?

    relationship您的表有四个可能的主键:

    • (id_1)

    • (id_2)

    • (id_1, id_2)

    • (id_2, id_1)

    根据定义,主键在您的表中必须是唯一的。事实上,它是识别记录的主要方法。但是,如果需要,还可以定义更多UNIQUE键,它们具有与主键相同的约束效果(差异相对较小,超出了此答案的范围):因此,实际上可以强制执行上述约束的任何组合。

    上述约束将分别: 将每个名称限制在关系的一侧不超过一次;将每个名字限制在关系的另一方不超过一次;最后两个将名称的每个组合限制在同一关系中不超过一次(不同之处仅在于存储索引的顺序)。如果表表示无向关系,那么显然第二个和第四个约束在语义上分别等同于第一个和第三个约束。

    一些例子:

    • 如果你的表代表“id_1是”的遗传父亲,id_2那么id_1可能有很多孩子。所以(id_1) 不能是主键,因为它不会唯一标识有多个孩子的父亲的记录。另一方面id_2,只能有一个遗传父亲(胚胎学进展除外),因此(id_2) 唯一标识一条记录并且可以是主键(也就是说,这种多对一关系也可以通过father_id列建模表中names)。其他两个(复合)键将允许孩子有多个父亲,因此一定是不正确的。

    • 如果您的表表示“id_1是”的父母,id_2那么父母可以有很多孩子孩子可以有多个父母(这称为多对多关系)。因此前两个约束是不正确的,必须在后两个之间进行选择(如前所述,不同之处仅在于索引存储的顺序——因此 MySQL 必须先定位第一列,然后才能查找第二列)。顺便说一句,在这种情况下,可能会考虑在表中添加一个额外的列,以relationship指示关系代表哪个父级;如果一个孩子只能有每个类型的一个父母,那么可以将主键定义为(child_id, parent_type).

    • 如果您的表格代表“id_1并且id_2已婚”,那么两者(id_1)(id_2)都是“候选键”,因为没有人可以与多个其他人结婚(至少在英国,一夫多妻制除外)。因此,可以定义(id_1)为主键定义第二个UNIQUE(id_2)。如前所述,人们可能希望以两种方式将记录放在表中——这些约束不会阻止这种情况。

  • MySQL 中有没有办法防止在插入期间创建重复关系(例如 0:4 和 4:0)?

    是的,可以使用触发器来做到这一点:但请注意上面所说的关于双向关系的内容(通常需要这种“重复”)。强制执行此类约束的触发器示例可能是:

    CREATE TRIGGER rel_ins BEFORE INSERT ON relationships FOR EACH ROW
    IF EXISTS (
      SELECT * FROM relationships WHERE id_1=NEW.id_2 AND id_2=NEW.id_1
    ) THEN
      SIGNAL SQLSTATE '45000'
             SET MESSAGE_TEXT = 'Reverse relationship already exists';
    END IF;;
    

    人们可能还想要一个类似的“更新前”触发器。

    这种约束可能是可取的情况是表表示“是”的父母,因为父母不能是他们孩子的孩子(但是,在这种情况下,可能值得注意的是,在这样的关系表中,实际上,人们可能希望更进一步并防止所有循环——例如,防止孩子成为其祖父母的父母)。同样,“邻接表”并不是执行这种约束的最佳模型——另一方面,“嵌套集”完全凭借其结构完全阻止了所有循环。

  • MySQL 对我来说默认为 InnoDB。这是您为我的场景推荐的数据库吗?

    InnoDB 的最大优势在于它完全符合ACID,因此提供了事务支持。如果您可能一次从多个位置写入数据库,这将特别有用。如果您只是要将一堆静态数据一次性加载到数据库中以供后续查询,那么它可能会比 MyISAM 慢一点。

于 2013-07-26T22:03:32.063 回答