65

我正在尝试在 MySQL 数据库中实现“一对一”关系。例如,假设我有一个用户表和一个帐户表。我想确保一个用户只能拥有一个帐户。并且每个用户只能有一个帐户。

我为此找到了两种解决方案,但不知道该使用什么,还有其他选择。

第一个解决方案:

DROP DATABASE IF EXISTS test;
CREATE DATABASE test CHARSET = utf8 COLLATE = utf8_general_ci;
USE test;

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    user_name VARCHAR(45) NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

CREATE TABLE accounts(
    id INT NOT NULL AUTO_INCREMENT,
    account_name VARCHAR(45) NOT NULL,
    user_id INT UNIQUE,
    PRIMARY KEY(id),
    FOREIGN KEY(user_id) REFERENCES users(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

在这个例子中,我在账户中定义了指向用户主键的外键。然后我将外键设为唯一,因此帐户中不能有两个相同的用户。要加入表,我将使用此查询:

SELECT * FROM users JOIN accounts ON users.id = accounts.user_id;

第二种解决方案:

DROP DATABASE IF EXISTS test;
CREATE DATABASE test CHARSET = utf8 COLLATE = utf8_general_ci;
USE test;

CREATE TABLE users(
    id INT NOT NULL AUTO_INCREMENT,
    user_name VARCHAR(45) NOT NULL,
    PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

CREATE TABLE accounts(
    id INT NOT NULL AUTO_INCREMENT,
    account_name VARCHAR(45) NOT NULL,
    PRIMARY KEY(id),
    FOREIGN KEY(id) REFERENCES users(id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

在此示例中,我创建了一个外键,它从主键指向另一个表中的主键。由于默认情况下主键是唯一的,这使得这种关系是一对一的。要加入表格,我可以使用它:

SELECT * FROM users JOIN accounts ON users.id = accounts.id;

现在的问题:

  • 在 MySQL 中创建一对一关系的最佳方法是什么?
  • 除了这两个之外,还有其他解决方案吗?

我正在使用 MySQL Workbench,当我在 EER 图中设计一对一关系并让 MySQL Workbench 生成 SQL 代码时,我得到一对多关系:S 这让我感到困惑:S

如果我将这些解决方案中的任何一个导入 MySQL Workbench EER 图,它会将关系识别为一对多 :S 这也令人困惑。

那么,在 MySQL DDL 中定义一对一关系的最佳方法是什么。有哪些选择可以实现这一目标?

4

3 回答 3

56

由于默认情况下主键是唯一的,这使得这种关系是一对一的。

不,这使得关系“一对零或一”。那是你真正需要的吗?

如果,那么您的“第二种解决方案”会更好:

  • 它更简单,
  • 占用更少的存储空间1(因此使缓存“更大”)
  • 维护2的索引更少,这有利于数据操作,
  • 并且(因为您使用的是 InnoDB)自然地对数据进行聚类,因此靠近的用户也将他们的帐户存储在一起,这可能有利于缓存局部性和某些类型的范围扫描。

顺便说一句,您需要制作accounts.id一个普通整数(不是自动增量)才能使其工作。

如果没有,请看下面...

在 MySQL 中创建一对一关系的最佳方法是什么?

好吧,“最佳”是一个重载的词,但“标准”解决方案与任何其他数据库中的解决方案相同:将两个实体(在您的情况下为用户和帐户)放在同一个物理表中。

除了这两个之外,还有其他解决方案吗?

从理论上讲,您可以在两个 PK 之间进行循环 FK,但这需要延迟约束来解决先有鸡还是先有蛋的问题,遗憾的是 MySQL 不支持这种问题。

如果我将这些解决方案中的任何一个导入 MySQL Workbench EER 图中,它会将关系识别为一对多:S 那也令人困惑。

我对那个特定的建模工具没有太多的实际经验,但我猜这是因为它是“一对多”的,其中“多”边通过使其独特而被限制为 1。请记住,“many”并不是“1 or many”的意思,而是“0 or many”的意思,所以“capped”版本真正的意思是“0 or 1”。


1不仅是附加字段的存储费用,还有二级索引的存储费用。而且由于您使用的 InnoDB总是对表进行集群,因此请注意,在集群表中二级索引比在基于堆的表中更昂贵。

2 InnoDB需要外键索引

于 2012-11-30T12:44:22.930 回答
9

您的第一种方法在帐户表中创建两个候选键:iduser_id

因此我建议第二种方法,即使用外键作为主键。这:

  • 少用一列
  • 允许您唯一标识每一行
  • 允许您将帐户与用户匹配
于 2012-11-30T12:17:00.103 回答
-4

下面的方法怎么样

  1. 创建表用户

    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(45) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  2. 创建具有唯一索引的表帐户,user_idaccount_id具有与用户/帐户的外键关系和主键user_id and account_id

    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(45) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  3. 创建表 user2account

    CREATE TABLE `user2account` (
      `user_id` int(11) NOT NULL,
      `account_id` int(11) NOT NULL,
      PRIMARY KEY (`user_id`,`account_id`),
      UNIQUE KEY `FK_account_idx` (`account_id`),
      UNIQUE KEY `FK_user_idx` (`user_id`),
      CONSTRAINT `FK_account` FOREIGN KEY (`account_id`) REFERENCES         `account` (`id`),
      CONSTRAINT `FK_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

尽管此解决方案在数据库中占用的空间最大,但也有一些优势。

  • 将 FK_Key 放在用户表或帐户表中是我期望的一对多关系(用户有很多帐户......)
  • 虽然这种user2account方法主要用于定义多对多关系,但添加 UNIQUE 约束将阻止创建除一对一关系之外的其他内容user_idaccount_id

我在此解决方案中看到的主要优点是您可以将工作划分为公司的不同代码层或部门

  • 部门 A 负责创建用户,即使没有账户表的写权限也是可能的
  • B 部门负责创建账户,即使没有用户表的写权限也可以这样做
  • 部门 C 负责创建映射,即使没有对用户或帐户表的写权限,这也是可能的
  • 一旦 C 部门创建了映射,如果不要求 C 部门先删除映射,则 A 部门或 B 部门都不能删除用户和帐户。
于 2017-05-29T12:12:05.300 回答