暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试官:谈谈你是如何建表以及如何优化表结构的?

Elasticsearch之家 2022-06-22
229


0.引言

一个优秀的开发者在性能的提升和资源的节约上是会不断追求,不断打磨的,我们在使用mysql时创建表结构时,更是需要我们理解每一种数据类型的区别,并且在不同的业务场景中选择适合的数据类型。

那么我们今天就来谈谈如何来优化表结构

1. 数据类型的优化

字段是组成表结构的基础,而针对数据类型做好优化,更是表结构优化的第一步。所以我们先来看如何对数据类型做好选型与优化。

1.1 mysql的数据类型

首先我们要知道mysql支持哪些数据类型,这些数据类型之间的区别是什么,然后我们才能针对不同的场景选择不同的类型。

mysql中一共支持10种数据类型

1.1.1 整数类型

整数类型分为以下5种 

通过上述我们知道tinyint是在所有整型中占用空间最小的,我们常常看到会用int(n)等形式来表示数据类型,其中的n的含义是该数据类型能够展现的最大宽度,比如10的宽度就是2,100的宽度就是3,n与数据的存储大小不相关,也就是说int(1)与int(11)所占用的空间都是4字节

1.1.2 浮点数类型

mysql中浮点类型有以下3种


decimal的大小取决于m,n的值,m表示整个数值的数字长度,n表示小数部分的数字长度,默认4个字节可以存储9个数字,小数点占1个字节,不满9个数字时占用大小如下所示 

举个例子:decimal(19,4),19/9=2......1,2*4=8字节,1个数字占1字节,再加上小数点1字节,所以总共占8+1+1=10字节

需要注意的是:mysql规定m<65,n<30

1.1.3 位类型

bit数据类型用于存储位字段值,支持1-64个长度,我们可以用bit(1)来表示布尔类型的数据

尽管从空间占用上来说,bit更小,但实际开发中我们更常用的是tinyint(1)来表示,而很少使用bit,主要是因为bit是在mysql5.x版本后的支持才逐渐完善,而历史开发的习惯和继承使得我们更喜欢使用tinyint

1.1.4 日期类型*

日期类型主要包含以下5种

1.1.5 字符类型*

字符类型包括如下几种:

需要注意的是,在mysql中已经很少使用4种text类型了,一般这类长文本数据会以文件形式存储到OSS,或者存储到es中间件中。

1.1.6 二进制字符串类型

与上述的文本类型对应,mysql中提供了4中二进制文本类型,实际开发中使用较少,这里罗列出来供大家参考

blob类型与text类型的区别在于:1、blob是以二进制存储数据,text是以文本存储数据 2、blob存储的数据只能整体读出 3、因为blob是二进制的,所以不用指定字符集,而text需要

1.1.7 枚举类型*

enum,与java一样,mysql中也有枚举类型,我们可以用枚举类型来表示状态、类型等枚举值

1.1.8 集合类型

set,不常用,一般用子表存储集合数据

1.1.9 JSON类型

json,用于存储json型对象、数组,比如存储bpmn工作流模版json型数据。但实际工作中很少使用mysql存储json数据,这是由mysql本身关系型数据库的本质决定的,更常使用天然支持json的mongodb,如果数据量大的还可以采用es存储

1.1.10 空间数据类型

geometry,point等,mysql中也很少存储空间坐标数据,同样更常使用mongodb、es来存储

1.2 数据类型优化原则

我们在了解了mysql支持的数据类型以及它们占用的空间大小和用途后,就可以接着进入我们今天的正题,如何优化表字段。我们从以下3个原则出发来进行讲解

1.2.1 越小越好

我们应当尽量使用最小的数据类型,所谓最小就是占用空间最小。能用smallint就不用int,能用tinyint就不用smallint

1、整型 比如‘是否删除’这样的标识字段,我们就可以用tinyint(1)来表示,而不要用int(1)来表示。上面已经解释了,这里面的1并不是其占用的空间大小,不要觉得tinyint(1)的大小和int(1)大小是一致的

整型的空间大小按从小到大是:tinyint<smallint<mediumint<int<bigint,能用小则用小

** 2、字符串** 字符串中我们最常用的是varchar和char,如电话号码、邮编这样的定长的字符我们就尽量使用char。并且其长度在满足业务的情况下,能小则小,比如邮编为6位,就不要定义为char(10),不要白白浪费这4个空间。

同时对于不确定长度的字符串,我们虽然使用varchar,但也不要盲目地定义为varchar(255),而是要根据实际的业务情况来定义最大长度,如果你不知道他的最大长度,那就去问产品经理、问业务,确定一个最大长度。

这里还要给大家补充几个知识点:

(1)varchar(n),当n<=255时,需要使用额外的1个字节保存长度,n>255时需要额外使用2个字节保存长度。 

(2)为了提高查询效率,及时长度不满255,mysql也会给varchar预留255的空间位置,也就是说varchar(1)预留的空间位置也是255,这里要注意预留位置不代表实际占用的大小,所谓预留是指将磁盘中连续的这一段空间位置留出来,这样这些数据是连续的,获取的时候效率就高。想想你们一家人,你哥住村头,你住村尾,你说村长过来通知你家去开会,你们是住在一起的时候通知的快还是没住在一起的时候通知的快?

(3)varchar在mysql5.6版本之前,变更长度到255以下改到255以上时,会导致锁表。所以我们都尽量建议将长度定到255以下 

(4)char的查询效率比varchar更高,所以能用char就用char。结合上面说的村头村尾的例子,想想为什么?

3、长文本数据 如上述所示,这类数据一般是文章或者其他文档文件,我们一般将其以文件形式存储到OSS服务器,然后在数据库中以varchar形式存储一个OSS上该文件的地址

4、时间类型 按照占用大小来看:date<timestamp<datetime。按照上述的原理,在满足业务的情况下尽量使用小的,但是时间类型比较特殊,我们考虑业务需要具有一定的前瞻性:

比如存储的日期需不需要到秒,业务上可能没有这个需求,但是未来做数据统计的时候需不需要。

或者项目运行的日期会不会超过2038年,因为timestamp最大只能表示2038年。这时就要使用datetime

5、枚举类型 使用enum类型或者数值类型来代替一些枚举型的字符串

6、ip地址 一般我们存储ip地址直接想到的是字符串,实际上可以通过inet_aton
函数将字符串型的ip数据转换为数值存储到数据库,这样占用的空间就会大大缩小,查询时再通过inet_ntoa
函数来转换

    select inet_aton('1.1.1.1')
    select inet_ntoa(16843009)
    复制

    1.2.2 越简单越好

    能够使用更加简单的类型就使用更简单的类型,因为简单的数据类型消耗的CPU资源更少。那么哪些类型更加简单呢?

    1、整型比字符串更简单,比如ip地址转换为整型来存储 2、日期类型比字符串更简单,比如避免使用字符串来存储日期 3、char比varchar更简单

    1.2.3 避免为空

    我们知道在对null值判断时,直接=null
    !=null
    是不生效的,而得通过is null
    is not null
    来判断。并且null值会让索引无法统计,因此我们在创建表字段时,能给默认值就尽量给默认值,特别是索引字段。

    2. 表结构优化原则

    2.1 适当的数据冗余

    适用场景:频繁查询且需要join两张或两张以上表才能获取的少量字段

    设计表结构时,如果某些字段需要join另一个表来查询,并且查询还比较频繁,那么我们就应该考虑在主表中冗余该字段,这样通过一张表查询所有数据,提高查询效率

    案例1:

    比如商品名称,商品ID等数据维护在商品表里,订单表中有商品ID。我们查询订单中必然要显示商品名称,如果每次都从商品表中关联获取名称,其效率肯定不如我们直接在订单表中冗余一个商品名称来得高

    当然这样的冗余要考虑业务需要,比如我们某些业务要求数据要实时显示,名称这类的信息可能会被更改,一旦更改了之前业务表中查询出来的名称也要是最新的,这种情况下,就需要我们得通过关联查询了,就不再是冗余数据能够解决的了

    有的同学可能会说了,我更新的时候把冗余字段一起更新了不就行了吗?嗯这倒是个好主意,但是得分情况,如果业务表中的数据特别的,那你要更新的冗余数据就会特别多,可能就会导致这个更新操作得不偿失。

    案例2:

    比如我们有一个运单表,每个运单下记录了多个运输的货物信息,那么同时也有了一个运输货物表,那么当我们要统计某段时间内的运输总量时,就需要通过运单表关联货物表,然后对货物表中的重量字段求和

    但实际上我们可以通过在运单中冗余一个总重量字段,甚至可能还有一个总金额字段,在插入货物的时候就求和并且记录到运单中了,这样统计时直接在运单中获取即可

    2.2 适当的拆分

    使用场景:表中存在占用空间较大但不常查询的字段,同时存在其他频繁查询的字段时

    当我们的表中存在某个或某几个占用空间大,比如text类型的,或者varchar(200+)类型的,我们需要经常查询这张表的某几个字段,但是这几个大字段却不需要时,我们就可以考虑把这几个大字段拆分到另一张表中,通过外键关联

    这样做的好处是,让这些经常查询的数据存放在相邻的数据块中,这样查询时可以提高效率(还是上述提高的村头村尾的例子),减少IO次数

    2.3 选择合适的字符集

    可能我们很多的开发中并没有在意字符集的选择,很多都是闭着眼给数据库设置utf8mb4
    ,然后完事了。

    但是实际上,针对不同的业务场景,选择适当的字符集可以大大提高我们的查询效率

    1、如果业务数据只有英文和数字,那么可以设置为latin1
    ,也就是拉丁字符集,这可以节约大量的存储空间。如果可以确定不需求存放多种语言的数据,就没有必要非得用utf8
    utf8mb4

    2、针对不同的业务表的不同数据情况,选择不同的字符集,以减少存储空间,从而提高查询效率

    3、有中文,就使用utf8mb4
    ,而不要用utf8
    。因为utf8
    最多支持3字节,而像一些生僻中文字和emojj表情是需要4字节的,就会导致存储错误bug,mysql官方在2010年发布utf8mb4
    也是为了解决这些bug。

    2.4 主键选择

    主键选择一般有两种方案:一是选择业务数据中的具有唯一性的自然主键,比如用户表中的身份证号,订单表中的订单号;二是通过主键生成算法生成一个与业务无关的主键,比如UUID

    我们更加推荐使用第二种方案,即通过主键生成算法来生成一个随机的主键,这样做有两种好处:

    1 与业务无关,更加容易维护 想象一下,如果是在多租户系统中,很多情况下为了满足用户公司的管理需求,我们允许用户输入自定义编号,比如租户A的用户输入了编号111,如果我们将这个唯一编号作为主键,因为是多租户系统,数据是隔离的,租户B的用户是看不到编号111的数据的,但是如果他刚好也创建了一个编号111时,就会发现数据库报错了,因为主键不允许重复,但是他却会纳闷了:我也没看到111被输入过呀,页面里没有显示呀。所以这样的情况下, 与业务无关的主键成为必要。2 一般主键生成算法是通用的,通用的处理方案对整体开发而言是能够高效节约时间的

    Elastic  


    ~




    文章转载自Elasticsearch之家,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论