# MySQL schema 设计中的陷阱

  • 太多的列

    MySQL 的存储引擎 API 工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列。从行缓冲将编码过的列转成行数据结构的操作代价是非常高的。MyISAM 的定长行结构实际上与服务器层的行结构正好匹配,所以不需要转换。然而,MyISAM 的变长行结构和 InnoDB 的行结构总是需要转换。转换的代价依赖于列的数量。当我们研究一个 CPU 占用非常高的案例时,发现客户使用了非常宽的表(数千个字段),然而只有一小部分列会实际用到,这时转换的代价就非常高。

  • 太多的关联

    MySQL 限制了每个关联操作最多只能有 61 张表,但是 EAV(实体-属性-值)数据库需要许多自关联。实际上少于 61 张表的情况下,解析和优化查询的代价也会成为 MySQL 的问题。一个粗略的经验法则,如果希望查询执行得快速且并发性好,单个查询最好在 12 个表以内做关联。

  • 全能的枚举

    一个枚举列要新增或则减少,都需要执行 ALTER TABLE 操作,在 5.0 版本以前 ALTER TABLE 是一种阻塞操作。在 5.1+ 如果不是在列表的末尾增加值也会一样需要 ALTER TABLE。在生产环境中,这可能会导致很长时间的阻塞

  • 变相的枚举

    枚举(enum)列允许在列中存储一组定义值的单个值,集合(set)则允许在列中存储一组定义值中的一个或多个值。当出现下面这种情况时,应该使用枚举

    create table..(
    is_default set('Y','N') not null default 'N')
    -- 当一条数据不会同时出现 Y 或则 N 时。
    
    1
    2
    3
  • 非此发明(Not Invent Here)的 NULL

    之前写了避免使用 NULL 的好处,并且建议尽可能考虑替代方案。即使需要存储一个事实上的 「空值」到表中时,也不一定非得使用 NULL,也许可以使用 0 、某个特殊值,或则空字符串作为代替

    但是不要走极端 !在一些场景中,使用 NULL 可能会比某个神奇的常数更好。例如 -1 代表一个未知的整数,可能导致代码复杂很多,并容易引入 BUG。

    下面是一个经常看到的例子

    create table...(
     dt datetime not null default '0000-00-00 00:00:00')
    
    1
    2

    伪造的全 0 值可能导致很多问题(可以配置 MySQL 的 SQL_MODE 来禁止不可能的日期,对于新应用这是个非常好的实践经验,它不会让创建的数据库里充满不可能的值)。值的一提的是,MySQL 会在索引中存储 NULL 值,而 Oracle 则不会