【问题标题】:Database schema confusing (Index and Constraints)数据库模式令人困惑(索引和约束)
【发布时间】:2012-12-03 07:07:43
【问题描述】:

我对架构的设计有点困惑,但在开始之前,让我先向您展示架构,

CREATE TABLE Person
(
    PersonID INT NOT NULL PRIMARY KEY,
    FirstName VARCHAR(50),
    LastName VARCHAR(50),
    -- some columns here..
    CONSTRAINT tb_idF INDEX (FirstName),
    CONSTRAINT tb_idL INDEX (LastName)
    -- or 
    -- CONSTRAINT tb_idL INDEX (FirstName, LastName)
    -- other constraints ...
);

CREATE TABLE JobDescription
(
    JobDescriptionID INT NOT NULL PRIMARY KEY,
    JobDescriptionName VARCHAR(50) UNIQUE
    -- some columns here..
    -- constraints ...
);

混乱就在这里,映射表:PersonJobDescription。目前,我有这个设计,

CREATE TABLE Person_JobDescription
(
    RECID INT AUTO_INCREMENT PRIMARY KEY,   -- for some special reasons
                                            -- I need to map to other table
    PersonID INT,
    JobDescriptionID INT,
    StartYear INT,                          -- year JobDescription was Appointed
    EndYear INT,
    CONSTRAINT tb_fk1 FOREIGN KEY (PersonID) 
        REFERENCES Person(PersonID),
    CONSTRAINT tb_fk2 FOREIGN KEY (JobDescriptionID) 
        REFERENCES JobDescription(JobDescriptionID),
    CONSTRAINT tb_uq UNIQUE (PersonID, JobDescriptionID)
);

但我有另一个想法,映射表的结构会是这样的

CREATE TABLE Person_JobDescription
(
    PersonID INT,           -- map these two columns on the other table
    JobDescriptionID INT,   -- ^^
    StartYear INT,          -- year JobDescription was Appointed
    EndYear INT,
    CONSTRAINT tb_fk1 FOREIGN KEY (PersonID) 
        REFERENCES Person(PersonID),
    CONSTRAINT tb_fk2 FOREIGN KEY (JobDescriptionID) 
        REFERENCES JobDescription(JobDescriptionID),
    CONSTRAINT tb_pk PRIMARY KEY (PersonID, JobDescriptionID)
);

当我针对上面的表创建和测试查询时,它们都返回相同的结果,并且性能也与我在小型数据库上测试的相同(有 50k 条记录)。我想知道这两个查询在大型数据库上的表现如何。

问题

  • 在大型数据库上您更喜欢映射表 (Person_JobDescription) 的两种模式中的哪一种?

按照指示,我不能在FirstNameLastName 上创建UNIQUE 约束。但我已经为这两列提供了一个索引。

  • 我将在表Person 上使用什么类型的索引?每列的索引或FirstNameLastName 的复合索引?
  • 我什么时候会使用单索引 INDEX (Col1)INDEX (Col2) 而不是 INDEX (Col1, Col2)

感谢您花时间阅读这个问题。

最好的问候,

德里克·弗洛斯

【问题讨论】:

    标签: mysql sql database schema


    【解决方案1】:

    第一种方法是我的偏好

    如果您需要一个依赖于 PersonJobDescription 的表,例如 AgentContact,您可以轻松链接到代理 Rec_ID,如果没有它,您必须开始跳过箍

    另一个原因是,如果要求每年持有一个 Person/JobDescription 会怎样? 在您知道自己在哪里之前,您将拥有一个仍然无法完成工作的四个 vakue 复合键。 规则复合主键应该是最后的手段,这将使您的设计更加灵活和有弹性。

    【讨论】:

    • 我也想接受这个答案,但不会。太感谢了!我给了你我的赞成票。
    【解决方案2】:

    我更喜欢第二种方法。通过在逻辑上不需要标识时使用代理 ID 号,您可以引入更多强制连接。这需要你“在整个数据库中追逐 ID 号”,这相当于“在整个数据库中追逐指针”的 SQL。追逐指针是 IMS 的特征,IMS 是关系模型打算取代的数据库架构之一。 (IMS 使用分层架构。)今天重新发明它是没有意义的。 (虽然 很多 人就是这样做的。)

    例如,如果您有五个级别的代理 ID 号,并且您想要一个人的姓名,则必须进行四次连接才能获得它。使用第二种方法,您只需要一个连接。如果您不想编写多列连接,请使用 CREATE VIEW 并只执行一次。

    性能易于测试。只需使用您喜欢的脚本语言生成几百万随机行,然后将它们加载到测试服务器中。您不仅会发现性能问题隐藏的位置,还会发现 CREATE TABLE 代码中的所有错误。 (您的代码不会按原样运行。)如果您还不了解 EXPLAIN,请了解它。

    至于索引,您可以在生成和加载的随机行上进行测试。如果用户始终提供名字,则 (first_name, last_name) 上的多列索引效果最佳。但是很多用户不会这样做,而是更喜欢按姓氏搜索。 (first_name, last_name) 上的多列索引对于喜欢按姓氏搜索的用户无效。你可以测试一下。

    仅出于这个原因,如果有两个单独的索引,一个用于名字,一个用于姓氏,对名字和姓氏进行索引通常更有效。


    追逐身份证号码是什么意思?

    这个问题背后不言而喻的设计模式是“每一行都必须有一个 id 号,并且所有外键都必须引用这个 id 号。”在 SQL 数据库中,它实际上是一种反模式。根据经验,任何让您无需考虑键就可以设计表格的模式在被证明是无辜的之前都应该被假定为有罪——在被证明不是之前,它应该被假定为反模式。

    create table A (
     a_id integer primary key,
     a_1 varchar(15) not null unique,
     a_2 varchar(15) not null
    );
    
    create table B (
      b_id integer primary key
      a_id integer not null references A (a_id),
      b_1  varchar(10) not null,
      unique (a_id, b_1),
    );
    
    create table C (
      c_id integer primary key,
      b_id integer not null references B (b_id),
      c_1 char(3) not null,
      c_2 varchar(20) not null,
      unique (b_id, c_1)
    );
    
    create table D (
      d_id integer primary key,
      c_id integer not null references C (c_id),
      d_1 integer not null,
      d_2 varchar(15),
      unique (c_id, d_1)
    );
    

    如果您需要关于表“D”的报告,并且该报告需要

    • 列 D.d_1 和 D.d_2,以及
    • A.a_1 和 A.a_2 列,

    您需要 3 个连接才能到达它。 (试试看。)你在追逐身份证号码。 (就像在 IMS 中追逐指针一样。)下面的结构不同。

    create table A (
     a_1 varchar(15) primary key,
     a_2 varchar(15) not null
    );
    
    create table B (
      a_1 varchar(15) not null references A (a_1),
      b_1  varchar(10) not null,
      primary key (a_1, b_1),
    );
    
    create table C (
      a_1 varchar(15) not null,
      b_1 varchar(10) not null,
      c_1 char(3) not null,
      c_2 varchar(20) not null,
      primary key (a_1, b_1, c_1),
      foreign key (a_1, b_1) references B (a_1, b_1)
    );
    
    create table D (
      a_1 varchar(15) not null,
      b_1 varchar(10) not null,
      c_1 char(3) not null,
      d_1 integer not null,
      d_2 varchar(15),
      primary key (a_1, b_1, c_1, d_1),
      foreign key (a_1, b_1, c_1) references C (a_1, b_1, c_1)
    );
    

    使用这种结构,同一个报表需要单个连接。

    select D.d_1, D.d_2, A.a_1, A.a_2
    from D
    inner join A on D.a_1 = A.a_1;
    

    【讨论】:

    • 每个first_name 都应该有一个last_name。所以你的意思是说每列上的索引:first_namelast_name与两列的复合索引相同?
    • 不,它们不一样。如果你有一个姓氏索引和一个名字索引,那么 dbms 可以使用它认为更快的任何一个。用户无论是按名字还是姓氏搜索都将得到快速响应。但是,如果用户按名字搜索,{last_name, first_name} 上的单个复合索引通常不会使用。
    • 你从哪里获得额外的连接?一个额外的键/索引是的,但是你只加入一个值,而不是多个值。代理键的真正好处是长寿和弹性,没有它们,业务类型不必做太多事情就可以使设计完全混乱,如果一开始就值得这样做,它将持续并改变。跨度>
    • @TonyHopkinson:编辑了我的答案。
    • 不能不同意这一点,只要围绕 a1 到 d1 的规则没有任何变化。我倾向于将弹性更改作为我的首选优化,现在如果它主要用于报告数据库,则根本没有争议。
    猜你喜欢
    • 2017-04-21
    • 2012-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 2022-01-17
    • 2013-01-04
    • 1970-01-01
    相关资源
    最近更新 更多