Mysql-如何建表更符合业务

1,657 阅读8分钟

索引

索引是 数据库中 对某一列或者多个列的值进行预排序的数据结构

也就是说你存放数据的时候 其实已经排列好顺序了。

索引可以理解为数据的 目录

InnoDB中,主键是一个特殊的索引字段

主键

InnoDB中, 每个表都有一个主键

如果你没有声明某个字段为主键,则表中 有一个 非空的唯一索引(Unique NOT NULL) 会默认成为主键

如果有多个 非空的唯一索引,则选择 第一个定义的索引为主键

如果没有 unique not null,则InnoDB会自动创建一个 6字节的指针,作为主键

索引组织表(Index Organized Table)

这种表是 由索引 “组织起来的” 表

对于InnoDB中,表都是根据主键顺序 存放的。

为什么不用平衡二叉树?

对于数据库来说 AVL树的查找性能已经足够好,效率足够高,但是为啥大家都没用呢?实际上就是因为

磁盘的读写 最小单位一般都是4kb(bloc),而一个avl的结点 最多就是数据库的表里面的一条记录,那显然这1条记录

是无论如何 都无法达到4kb的,这样的读写效率对一个数据库软件来说 是无法忍受的。

所以 才有了 B Tree。B tree和 Binary Tree 二叉树 没有任何关系哈, 只是坊间传闻,B tree的发明者 姓名是B 所以才叫 B Tree

B tree的一个结点 就可以存放多条记录了, 可以解决AVL的 存储效率问题。

对于B tree的 每一个结点内部来说 都是线性搜索

相比与AVL来说,B tree 不需要旋转就可以保证树的平衡,并且树的高度会变小,磁盘效率会变的很高。

但是对于数据库来说,我们常用的是范围查找,比如查找 年龄段 5-20岁的人,这样的范围查找 对于 B tree 来说效率就很低了

所以在这之上,才有了B + Tree。

B+ Tree 实际上把 数据和索引分开了,讲白了 B+Tree的最后一层 就全部都是数据,而且这些数据是按照线性的链表的结构 连起来,而 非最后一层 都是索引层。

InnoDB 索引 即 数据

一般来说,B+ 树的高度 一般就是2-4层,查找速度极快。

主索引 也叫 Clustered Index,也叫 聚簇索引

来看一下这张表,其中id 就是主键 自增长的 也就是主索引 image.png

实际上这张表在存储的时候 就变成了下图的样子:

image.png

除了最后一层的叶子结点, 上面的每一层 都是 单向链表

且每一层的每个page中 都存在着多个结点,这些结点的key 其实就是id 也就是主索引,而value 则是对应的page

举例,我们要查找 id为5的元素:

在root page3 中,5 因为是大于4的, 所以我们下一跳 就去到 page5 ,id=5 显然大于4而小于6 所以 我们下一跳就去page8

注意到page8 这里就是最后的叶子结点了,这边直接链表顺序查找到对应的id为5的结点即可。

这里要注意的是 最后的叶子结点 是一个双向链表。 且这些叶子结点中 存储的就是行数据

辅助索引 SecondaryIndex

1.每张表可以有多个辅助结点 2.叶子结点 不包含行数据 3.叶子结点记录了行数据的主键,用来指示数据位置

讲白了,还是前面的表,假设我们把letter字段 作为辅助索引,则

叶子结点存储的 不再是行数据, 而是 主键的位置,换句话说,对于辅助索引来说

她的叶子结点里面的每一个元素, key 自然就是辅助索引本身 而value 则是辅助索引所对应的主键的值

InnoDB中的变长列

长度不固定的数据类型:varchar varbinary blob text

占用空间大于768byte的不变长类型: char

变长编码下的 char

行溢出数据

一般而言 InnoDB 每个数据页 也就是每个page 的容量是有限的,一般都是16kb 这就会导致 数据字段也是有限的。

所以当数据字段过大的时候 就会启动行溢出机制

这种机制 会把超长字段 放到单独开辟的数据页

例如我们弄个text字段,里面直接放一部小说 这显然会超过16kb。

例如下图:

image.png

假设col1 太长了,这就会导致 我们的叶子结点 每个结点里面 只有1条数据,这样的查找效率就实在是太低了。

解决起来也很简单

image.png

我们单独弄一个blob页 来存储这个过长的字段即可

这样我们的b+树的 叶子结点 就可以存放多条数据了。

索引的 “左侧用法”

看如下这张表:

image.png

id 主键 自增长,然后 从左到右 分别是商店名称,商店地址,商店均价。

我们创建一个辅助索引

 create index info_shop_index
    on info (shop_address, shop_name, shop_price);

假设我们的sql语句是写成

select * from info where shop_address=“nanjing”

虽然我们没有单独给shop_address 建立索引,但是因为 shop_address 是这个辅助索引的最左侧的值,

所以 上述的sql语句 实际上也有 索引的效果。 总结起来就是

联合索引和代替最左侧字段的单独索引

但是如果你的sql语句写成

select * from info where shop_name=“kfc” and shop_price="30"

这样就没有索引的效果了,查询的效率就会很低

同样的,下面的sql语句 也是一样 ,会丢失索引的查询效率 select * from info where shop_address=“nanjing” and shop_price="30"

但是如果是 select * from info where shop_address=“nanjing” and shop_name="kfc"

则索引的效果还在

总结起来就是

带头大哥不能死,中间兄弟不能丢

前缀索引

举个例子,假设我们有一个用户表,这个表里面 有个col是 email, 邮箱地址 大家都知道哈, 通常是很长的一个字符串,如果对这个email 建立索引的话,会有个问题就是 这个索引占用的空间可能太大了,因为email的字段比较大,

想解决这个问题就是

可以使用前缀索引了。 这样建立索引的时候 只会 取这个col的 前面几个字符,从而可以大大减少空间

形如:

alter table user add index index2(email(6))

再比如,我们假设要对 南京市 做一个表,其中有个字段是身份证号,对这个col 做索引会有啥问题呢?

同一个地区的人的身份证号 前几位 都是一样的 比如南京 可能就是 320525

那这样的索引查询效率 就比较低了,解决这个问题可以

  1. 倒序存储,我们在存数据库的时候 把身份证号倒着过来存就可以了,这样再使用前序索引 效率就很高了
  2. 新建一个hash字段,对 身份证号这个col 再建一个col 用来存放 身份证号的 hash值

字符串对索引的影响

like %关键字% like %关键字 会让索引失效的

原因很简单啊,你左边的字符不确定 索引肯定是失效的。 全模糊和右模糊 就是 让无效索引查询了。

左模糊才可以使用索引

非要 全模糊查询咋办? 假设你的业务量很大了,全模糊查询很慢,那么只能用 es 这种方案了。或者mysql 自带的搜索引擎 也可以,

约束数据

建表的时候 有一些配置 可以帮主我们约束 写入的数据

主键: 唯一 不为null

unique key: 唯一

问题 就是 这两个东西 约束插入的时候 性能开销 有点大(原因显然是插入的时候要检查 插入的数据是不是唯一的,但是也不用惧怕,毕竟大部分的应用都是强读弱写的)

外键: 外键可以对数据的正确性实现约束

不过有些公司一般不会让大家使用外键,因为这个东西 在线上 如果涉及到脏数据的处理 则会很麻烦,并且性能也很一般

触发器:这个东西用的比较少,因为你把业务全写在mysql的存储过程里面,后面很难维护!

使用不存在的数据表

使用 视图 可以创建不存在的虚拟表。原理其实就是预设一个select 语句,查询的结果作为虚拟表的数据。

视图算法的选择:

MERGE:把视图预设的sql 合并到你的sql中

TEMPLATE: 将视图作为临时表(中间结果) 来处理

大部分情况下,MERGE的算法是比TMP要快。

当然还有第三种 undefined 那就是让mysql 自己来决定 用哪种算法

另外,MERGE这种算法 很多时候 也用不了,例如 sql中包含:

聚集函数,distinct,group by, having,union,子查询

总结起来就是 :动态的结果是无法使用merge的

其他的建表技巧

增加每页数据量: 尽量做到冷热数据分离,减小表的宽度,为啥? 宽度变小了,数据量就多了啊,查询效率就高了

比如一个订单表,十几个字段,其实经常查的 也就那么4-5个字段,那其实就可以建两个表, 经常查询的字段 放一个表(热表),另外不经常用的 放一个表(冷表)

避免行溢出 : BLOB 和TEXT 这种列 尽量分离到单独的扩展表中

控制b+树的高度 : 尽量控制单表数据量的大小,不要超过500w。