MySQl 优化数据库结构

25 阅读6分钟

拆分表:冷热数据分离

举例1: 会员members表 存储会员登录认证信息,该表中有很多字段,如id、姓名、密码、地址、电话、个人描述字段。其中地址、电话、个人描述等字段并不常用,可以将这些不常用的字段分解出另一个表。将这个表取名叫members_detail,表中有member_id、address、telephone、description等字段。

这样就把会员表分成了两个表,分别为 members表 和 members_detail表 。

创建这两个表的SQL语句如下:

CREATE TABLE members
(
    id              int(11) NOT NULL AUTO_INCREMENT,
    username        varchar(50)  DEFAULT NULL,
    password        varchar(50)  DEFAULT NULL,
    last_login_time datetime     DEFAULT NULL,
    last_login_ip   varchar(100) DEFAULT NULL,
    PRIMARY KEY (Id)
);
CREATE TABLE members_detail
(
    Member_id   int(11) NOT NULL DEFAULT 0,
    address     varchar(255)     DEFAULT NULL,
    telephone   varchar(255)     DEFAULT NULL,
    description text
);

如果需要查询会员的基本信息或详细信息,那么可以用会员的id来查询。如果需要将会员的基本信息和详细信息同时显示,那么可以将members表和members_detail表进行联合查询,查询语句如下:

SELECT *
FROM members
         LEFT JOIN members_detail on members.id = members_detail.member_id;

通过这种分解可以提高表的查询效率。对于字段很多且有些字段使用不频繁的表,可以通过这种分解的方式来优化数据库的性能。

增加中间表

举例1: 学生信息表 和 班级表 的SQL语句如下:

CREATE TABLE `class`
(
    `id`        INT(11) NOT NULL AUTO_INCREMENT,
    `className` VARCHAR(30) DEFAULT NULL,
    `address`   VARCHAR(40) DEFAULT NULL,
    `monitor`   INT     NULL,
    PRIMARY KEY (`id`)
) ENGINE = INNODB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;
CREATE TABLE `student`
(
    `id`      INT(11) NOT NULL AUTO_INCREMENT,
    `stuno`   INT     NOT NULL,
    `name`    VARCHAR(20) DEFAULT NULL,
    `age`     INT(3)      DEFAULT NULL,
    `classId` INT(11)     DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE = INNODB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;

现在有一个模块需要经常查询带有学生名称(name)、学生所在班级名称(className)、学生班级班长(monitor)的学生信息。根据这种情况可以创建一个 temp_student 表。temp_student表中存储学生名称(stu_name)、学生所在班级名称(className)和学生班级班长(monitor)信息。创建表的语句如下:

CREATE TABLE `temp_student`
(
    `id`        INT(11) NOT NULL AUTO_INCREMENT,
    `stu_name`  INT     NOT NULL,
    `className` VARCHAR(20) DEFAULT NULL,
    `monitor`   INT(3)      DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE = INNODB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8;

接下来,从学生信息表和班级表中查询相关信息存储到临时表中:

insert into temp_student(stu_name, className, monitor)
select s.name, c.className, c.monitor
from student as s,
     class as c
where s.classId = c.id

以后,可以直接从temp_student表中查询学生名称、班级名称和班级班长,而不用每次都进行联合查询。这样可以提高数据库的查询速度。

增加冗余字段

设计数据库表时应尽量遵循范式理论的规约,尽可能减少冗余字段,让数据库设计看起来精致、优雅。 但是,合理地加入冗余字段可以提高查询速度。

表的规范化程度越高,表与表之间的关系就越多,需要连接查询的情况也就越多。尤其在数据量大,而且需要频繁进行连接的时候,为了提升效率,我们也可以考虑增加冗余字段来减少连接。

这部分内容在《第11章_数据库的设计规范》章节中 反范式化小节 中具体展开讲解了。这里省略。

优化数据类型

情况 1 :对整数类型数据进行优化。

遇到整数类型的字段可以用 INT 型 。这样做的理由是,INT 型数据有足够大的取值范围,不用担心数据超出取值范围的问题。刚开始做项目的时候,首先要保证系统的稳定性,这样设计字段类型是可以的。但在数据量很大的时候,数据类型的定义,在很大程度上会影响到系统整体的执行效率。对于 非负型 的数据(如自增ID、整型IP)来说,要优先使用无符号整型 UNSIGNED 来存储。因为无符号相对于有符号,同样的字节数,存储的数值范围更大。如tinyint有符号为-128-127,无符号为0-255,多出一倍的存储空间。

情况 2 :既可以使用文本类型也可以使用整数类型的字段,要选择使用整数类型

跟文本类型数据相比,大整数往往占用 更少的存储空间 ,因此,在存取和比对的时候,可以占用更少的内存空间。所以,在二者皆可用的情况下,尽量使用整数类型,这样可以提高查询的效率。如:将IP地址转换成整型数据。

情况 3 :避免使用 TEXT BLOB 数据类型

情况4:避免使用ENUM类型

情况 5 :使用 TIMESTAMP 存储时间

情况 6 :用 DECIMAL 代替 FLOAT DOUBLE 存储精确浮点数

总之,遇到数据量大的项目时,一定要在充分了解业务需求的前提下,合理优化数据类型,这样才能充分发挥资源的效率,使系统达到最优

优化插入记录的速度

MyISAM

InnoDB引擎

使用非空约束

count(列名)会忽略null

is not null索引会失效

存储需要占用一个bit的空间

省去对Null值的字段判断是否为空的开销

小结

上述这些方法都是有利有弊的。比如:

  • 修改数据类型,节省存储空间的同时,你要考虑到数据不能超过取值范围;
  • 增加冗余字段的时候,不要忘了确保数据一致性;
  • 把大表拆分,也意味着你的查询会增加新的连接,从而增加额外的开销和运维的成本。

因此,你一定要结合实际的业务需求进行权衡。

大表优化

限定查询的范围

禁止不带任何限制数据范围条件的查询语句。 比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;

读/写分离(主从复制)

经典的数据库拆分方案,主库负责写,从库负责读。

垂直拆分

当数据量级达到 千万级 以上时,有时候我们需要把一个数据库切成多份,放到不同的数据库服务器上,减少对单一数据库服务器的访问压力。

垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。

垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起 JOIN 操作。此外,垂直拆分会让事务变得更加复杂。

水平拆分