目的:支持可变的属性

    可扩展性是程序员所追求的。通常一张表对应一个实体,但是需求中也可能两个实体他们继承同一个父类实体,他们由相同的属性,也有不同属性。
    bug和feature_request,有相同的属性,我们抽出一个父类issue。
关系示意图

反模式:使用泛型属性表(EVA)

    对于程序员来说,需要支持可变的属性时,第一发应就是创建一张表(issue_attributes),它有三个字段

  1. 实体 : 指向父表的外键
  2. 属性 : varchar类型,存储属性名字
  3. 值 :存储对应属性的值
    关系示意图
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    create table issue(
    issue_id serial primary key
    );
    create table issue_attribute(
    issue_id bigint unsigned not null,
    attr_name varchar(100) not null,
    attr_value varchar(100) not null,
    primary key(issue_id, attr_name),
    foreign key(issue_id) references issue(issue_id)
    );
查询某一个属性

    假如你要查询bug的描述信息,显得比较啰嗦,还不清晰

1
select issue_id, attr_value as 'description' from issue_attribute where attr_name = 'description'
数据完整性

    你无法保证attr_value是否是一个有效值

无法声明属性类型

    无法使用SQL的数据类型,比如对日期、金钱等格式内容都只能保持为字符串类型;

无法确保引用完整性

    你不能对attr_value声明外键约束,比如说status,你不能保证status的值一定存在status表中

无法确定属性名

    假如有两条数据,一条的attr_name是sex,一条attr_name是gender,都是表示性别

查询结果中有多个属性时,异常困难
1
2
3
4
5
6
select i.issue_id,
i1.attr_value as 'date_reported',
i2.attr_value as 'status'
from issue i
left join issue_attribute i1 on i.issue_id = i1.issue_id and attr_name = 'date_reported'
left join issue_attribute i2 on i.issue_id = i2.issue_id and attr_name = 'status'

如何识别反模式

当出现以下情况时,可能是反模式

  1. 数据库不需要修改元数据库(表中的列属性)就可以扩展。还可以在运行时定义新的属性。
  2. 查询是连接数量非常多,且连接的数量可能会达到数据库的限制时,你的数据库的设计可能是有问题的。
  3. 普通的报表查询变的及其复杂甚至不且实际。

合理使用反模式

优点:

  1. 表中的列很少;
  2. 新增属性时,不需要新增列。不会影响现有表的结构;
  3. 存储的字段内容不会为空值。

缺点:上面列了一大段

如果真的有非关系数据管理需求,那最好使用nosql数据库

解决方案:模型化子类型

单表继承

    所有属性都在一个单表上保存,使用issue_type来标识哪一个子类,子类属性必须支持null。
    缺点:

  1. 当程序加入新对象时,必须修改数据库来适应这些新对象,而张表的列的数量是有限制的。
  2. 没有任何的元信息来记录哪个属性属于哪个子类型。
实体表继承

    为每个子类型创建一张独立的表,每个表包含哪些属于基类的共有属性,同时也包含了子类型特殊化的属性。
    优点:

  1. 你可以像使用普通表那样来使用
  2. 相比于单表继承,新增对象时,你只需要新增表,而不需要修改之前的表结构

    缺点:

  1. 难将通用属性和子类特有属性区分开来。因此,如果将一个新的属性增加到通用属性中,必须为每个子类表都添加一遍。
类表继承

    把表当成面向对象里的类,这可能也是最容易想到的吧,创建一张基表存储子类相同的字段,每一个子类创建一张表,添加一列执行基表的外键

半结构化数据模型

    如果有很多子类型或者必须经常增加新的属性支持,那么可以用一个BLOB列来存储JSON数据,缺点是,这样的结构中sql无法获取某个指定的属性。你必须或者整个blob字段并通过程序去解释这些属性。
    当你需要绝对的灵活性时,可以使用这个方案。

你接手一个EAV项目

    你无奈接手一个EAV项目,你需要衡量在数据库中处理数据好还是在程序代码中处理好,比如说查询

1
select * from issue_attribute where issue_id = 1444;

查出所有行,在程序代码中遍历,明显要比在数据库中使用无限的联结查询要好。