MySQL

MySQL索引与底层结构

底层数据结构

内容读取

JAVA 调度cpu,读取RAM内容,RAM从硬盘以页的形式载入,页再载入的内存之中,一次从磁盘中读取的数据为页的整数倍,但页与页之间会存在缺页中断,因此在数据库存储中定一个一个节点为一页, 一页默认大小为4k

索引

帮助MySQL高效获取数据的排好序的数据结构;索引存储在文件里

磁盘存取:寻道时间(速度慢,费时)+旋转时间(速度较快) 寻道时间即磁头移动时间,磁道 旋转时间即扇区上寻址; kafka的优化,大部分数据存取都是在落在旋转上,速度快

索引结构

可以认为一个节点的操作就伴随着一次IO

二叉树 容易单边树过深,特别是在数据库自增主键的情况下,和顺序查找没有区别,时间复杂度是一样的

图示

image.png

红黑树 随着数据的增多,其深度h越深,不可控

图示

image.png

HASH 根据hash值分配变量地址,进行存储,可以快速寻找到某一个变量值,但不适用于范围查找

BTREE 相较于红黑树 其宽度增长,对应的纵向深度h可控

度(Degree) 节点的数据存储个数 定义度为4 其实并不会存储达到度的个数,达到一定阈值自然会延伸叶节点

叶节点具有相同的深度

叶节点的指针为空

m节点中的数据key从左到右递增排列

图示

image.png

底层存储结构 B+Tree(B-Tree变种) 非叶子节点不存储data,只存储key,可以增大度 叶子节点不存储指针 顺序访问指针,提高区间访问的性能

图示

image.png

将数据挂载在叶子节点上,不影响查询效率,叶子节点本身已是最后一次查询了

区别B-Tree,有指针指向下一个节点,方便范围查询

性能分析 一般使用磁盘IO次数评价索引结构的优劣

预读:磁盘一般会顺序向后读取一定长度的数据(页的整数倍)放入内存

局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用

B+Tree节点的大小设为等于一个页,每次新建节点直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,就实现了一个节点的载入只需一次I/O

B+Tree的度d一般会超过100,因此h非常小(一般3到5之间)

存储引擎 MyISAM

索引文件与数据文件是分离的

表锁

索引实现非聚集 主键索引图示 叶子节点存放指针

image.png

非主键索引图示 与主键索引一样

image.png

InnoDB

行锁

数据文件本身就是索引文件

表数据文件本身就是按B+Tree组织的一个索引结构文件

聚集索引-叶节点包含了完整的数据记录

图示

image.png

为什么InnoDB表必须有主键,并且推荐使用整型的自增主键?

B+Tree的生产依赖于某个数进行比较,生成两边,在未定义主键的情况下会在后台默认创建一个主键。但不建议这么做,从可维护性及易读性上考虑,都应该显示指定

使用整型:方便数的比较

自增:有利于在叶子节点生成连续的物理块,便于数据存储是连续的

为什么不建议用UUID生成主键?

非整型,存储底层会使用ASCII码比较,耗时长于整型

数据占用空间大于整型,因此一个节点能容纳的数据即度小于整型存储的

最主要的,无法形成自增,其在插入数据的时候,要寻找其uuid的两边值并插入其中,导致叶子节点需要重新演化生成。极大的插入影响效率。

注意:分表使用的雪花和基因算法,都是维持一种上升的趋势的

为什么非主键索引结构叶子节点存储的是主键值?

维护数据的时候,只需要维护一份

节省空间

SQL底层执行原理

关注my.cnf

连接 session load权限 并 缓存,此时修改数据库用户权限,也不会立马反馈。 默认长链接 8h。

show processlist 查看连接进程号,kill id 号

server缓存层 适合 几乎不变动的表数据

引擎层 会存储热点数据 bufferpool 缓存 采用 LRU

antlr4 分库分表 分布式 rollback

binlog建议 row; statement可能导致主从不一致 update … set where a=xx or b=xx limit 1

MySQL设计原则

建表规约

  1. 代码先行,索引后上
  2. 一般主体业务功能开发完毕,把涉及到该表相关SQL都要拿出来分析之后再建立索引
  3. 命名规范 表名、字段名、索引名 参考《阿里巴巴Java开发手册》

数据类型

  1. 确定合适的大类型:数字、字符串、时间、二进制;
  2. 确定具体的类型:有无符号、取值范围、变长定长等
  3. 尽量把字段定义为NOT NULL,避免使用NULL。

说明:若为少数数据,可不使用isNull判断,通过程序过滤

具体细则

  1. 如果整形数据没有负数,如ID号,建议指定为UNSIGNED无符号类型,容量可以扩大一倍。
  2. 建议使用TINYINT代替ENUM、BITENUM、SET。
  3. 避免使用整数的显示宽度(该宽度只是0填充适用),也就是说,不要用INT(10)类似的方法指定字段显示宽度,直接用INT。
  4. DECIMAL最适合保存准确度要求高,而且用于计算的数据,比如价格。但是在使用DECIMAL类型的时候,注意长度设置。
  5. 建议使用整形类型来运算和存储实数,方法是,实数乘以相应的倍数后再操作。

    整数通常是最佳的数据类型,因为它速度快,并且能使用AUTO_INCREMENT

  6. 建议用DATE数据类型来保存日期。MySQL中默认的日期格式是yyyy-mm-dd

    当数据格式为TIMESTAMP和DATETIME时,可以用CURRENT_TIMESTAMP作为默认(MySQL5.6以后),MySQL会自动返回记录插入的确切时间

    TIMESTAMP是UTC时间戳,与时区相关。

    DATETIME的存储格式是一个YYYYMMDD HH:MM:SS的整数,与时区无关,你存了什么,读出来就是什么。

  7. 字符串的长度相差较大用VARCHAR;字符串短,且所有值都接近一个长度用CHAR。

    CHAR和VARCHAR适用于包括人名、邮政编码、电话号码和不超过255个字符长度的任意字母数字组合。那些要用来计算的数字不要用VARCHAR类型保存,因为可能会导致一些与计算相关的问题。换句话说,可能影响到计算的准确性和完整性。

  8. 尽量少用BLOB和TEXT,如果实在要用可以考虑将BLOB和TEXT字段单独存一张表,用id关联。

索引规约

核心思想

  1. 尽量利用一两个复杂的多字段联合索引,抗下你80%以上的查询,然后用一两个辅助索引尽量抗下剩余的一些非典型查询,保证这种大数据量表的查询尽可能多的都能充分利用索引,这样就能保证你的查询速度和性能了!
  2. 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
  3. 联合索引尽量覆盖条件

    比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原则

  4. 不要在小基数字段上建立索引

    索引基数是这个字段在表里总共有多少个不同的值。例:性别 男或女 基数为2

  5. 尽量对基数较大的值添加索引,才能发挥b+tree的优势来
  6. 长字符串可以采用前缀索引
  7. where与order by冲突时优先where

    因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可能会小很多。

  8. 一般范围查找的条件都要放在最后
  9. 定时任务刷某些字段值 例如:判断用户最近7天是否活跃 不要根据时间字段,而是通过记录标识

Explain的使用

MySQL优化