深入理解 Mysql -- 索引底层和性能优化

Mysql底层与优化

  • 数据的存储、磁盘IO与索引
  • 索引结构
    • 二叉树(红黑树/AVL)
    • HashMap
    • BTree
    • B+Tree
  • 存储引擎
    • MyISam(非聚集索引)
    • InnoDB(聚集索引)
  • 锁与优化
    • 锁优化
  • SQL优化
    • 优化原则
    • join
    • order by
  • 影响性能因素

数据的存储、磁盘IO与索引

  • mysql 数据以记录为单位,离散地存储在硬盘上,我们依靠存储地址来找到记录
  • 很多时候我们没办法直接找到想要的数据。因为通常我们的需求形如:找的学号为2016060666的学生的记录;而不是:找到磁盘中以0x00123456位置开头的记录
  • 这时我们对所有记录进行轮询,依次访问每一条记录的学号是不是我们要查找的学号,这样的查找效率极低
  • 磁盘IO是非常耗时的操作,因为每访问一个数据就要做一次磁盘IO,一次磁盘IO包括磁头寻道和等待扇区
  • 解决办法就是让磁盘在做少次的IO后找到我们要的记录,将查找条件和记录地址建立联系,在不同场景下建立有利于查找的数据结构,如二叉树、HashMap等
  • 能够帮助我们高效查找数据的数据结构就是索引,索引能够提高查找效率,但是减低写效率

深入理解 Mysql -- 索引底层和性能优化_第1张图片

索引结构

二叉树(红黑树/AVL)

  • 利用二分查找树或红黑平衡树可以快速找到我们要找的元素,但是对于数据库来说,表的记录往往是会不断增长的,即树的结构收到数据和插入次序的影响,此时得到的树不理想

深入理解 Mysql -- 索引底层和性能优化_第2张图片

  • 可以看出要想的到一颗理想的二叉树,那么索引值和插入次序条件将会十分苛刻,绝大部分场景下等不到接近理想的二叉树
  • 红黑树/二叉平衡树情况比二叉树好一些,但是数据规模大的时候,树的深度会很大,意味着依然需要做很多次磁盘IO,效果依然不理想

HashMap

HashMap一次查找的时间复杂度是O(1),但是只能做等值查询,做不到范围查询,在很多场景下依然不适用

BTree

  • 针对减小树的深度,我们可以对树的节点做横向扩容,每个节点可以存储多个数据,而且可以拥有多个孩子节点,这样我们每次IO能加载很多数据到内存中,在内存中查找的时间和磁盘IO相比几乎可以忽略不计
  • 但是横向扩容是有限度的,因为磁盘读写的单位是扇道,每个扇道的容量往往对应操作系统的一个页面的大小(一般是4K),即每次只能读取一个页面的数据,多了一次读不完,少了没有充分利用一次磁盘IO
  • 所以一个节点不宜放太多数据,mysql默认一个节点大小是16K(固定值),这样树的高度在1~3之间,在大数据的规模下只需要极少次数的磁盘IO就能找到数据

深入理解 Mysql -- 索引底层和性能优化_第3张图片

B+Tree

  • mysql索引用得最多的其实还是B+Tree,因为他是对Btree做的优化
  • B+Tree把所有数据放在叶子节点上,非叶子节点只放索引(冗余),这样我们可以在一个页面大小的限制下存下更多的key
  • B+Tree用指针把叶子节点连到了一起,这样有利于做范围查询

B+Tree

存储引擎

MyISam(非聚集索引)

  • 非聚集索引:索引与数据分开,索引对应的值是数据的地址
  • 一张数据表分成三个文件
  • xx.frm文件存放表结构
  • xx.MYI文件存放表的索引
  • xx.MYD文件存放表数据
  • 主键索引和非主键索引(非主键索引的值是数据的地址):

深入理解 Mysql -- 索引底层和性能优化_第4张图片

InnoDB(聚集索引)

  • 聚集索引:将表数据按照B+Tree结构存储,索引对应的值就是数据
  • 一张数据表分成两个文件
  • xx.frm文件存放表结构
  • xx.idb存放数据+索引
  • 主键索引和非主键索引(非主键索引的值是主键):

深入理解 Mysql -- 索引底层和性能优化_第5张图片

  • 因为InnoDB是聚集索引,数据是按主键根据B+Tree组织的,所以InnoDB一定要有主键
  • 推荐使用整形自增主键:整形在存储上比字符串占用更少的字节,有利于索引的横向存储,而且整形比大小比字符串比大小快;自增的主键在插入数据时是直接插入到树的末尾,可以避免树的分裂

锁与优化

  • 锁包括表级锁、行级锁、页级锁、间隙锁等
  • InnoDB默认支持表级锁和行级锁,MyIsam默认支持表级锁
  • 表级锁资源消耗较小,不会产生死锁,但粒度大,并发不高
  • 行级锁粒度小,有利于对表数据的并发操作,但资源消耗较大,很容易产生死锁

lock table user read; // 给user表上读锁,读锁是共享锁
lock table user write; // 给user表上写锁,写锁是排他锁
unlock tables; // 解锁
show status like ‘table%’ // 查看表级锁的争用状态变量
show status like ‘InnoDB_row_lock%’ // 查看行级锁的争用状态变量

锁优化

  • 尽可能让数据检索通过索引完成
  • 合理设计索引
  • 尽量减小事务的大小
  • 尽量使用较低级别的事务隔离

SQL优化

优化原则

  • 尽量选取区分度高的字段作为索引
  • 仅仅使用最有效的过滤条件 -> key length
  • 尽可能避免复杂的jion和子查询 -> 锁资源
  • explan 命令:查询优化神器,查看执行计划(QEP, Query Execute Plain),查看每一列的属性,以优化SQL语句
  • explain 列属性:
  • id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
  • select_type: SELECT 查询的类型.
  • table: 查询的是哪个表
  • partitions: 匹配的分区
  • type: join 类型
  • possible_keys: 此次查询中可能选用的索引
  • key: 此次查询中确切使用到的索引.
  • ref: 哪个字段或常数与 key 一起被使用
  • rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
  • filtered: 表示此查询条件所过滤的数据的百分比
  • extra: 额外的信息

join

  • A join B where A.id=B.id 是先查询到A的id集,再拿A的id集去检索B
  • 如果大于两个表 join,会将前边 jion 的结果放入 join_buffer,再继续 join 其他表

深入理解 Mysql -- 索引底层和性能优化_第6张图片

  • 如果 join_buffer 不够大,join 结果会被放入磁盘,再 join 就需要进行磁盘IO
  • show variables like ‘join_%’ // 查看 join_buffer 的大小
  • 优化:
  • 永远使用小结果集驱动大结果集
  • 保证被驱动表的 join 字段能被索引到
  • 加大 join_buffer_size

order by

  • 在条件字段已经建立 B+Tree 索引情况下,数据已经有序
  • 在条件字段没有名字索引的情况下,mysql 底层实现 order by 的两种方式(自动选择)
    buffer 足够大时(一次磁盘批量IO):将所需字段全部取到内存,再取出条件字段和记录在内存中的地址,对条件字段进行排序,排好序后用指针从内存取数据返回
    buffer 不够大时(两次磁盘批量IO):将条件字段与记录地址取到内存,根据条件字段进行排序,排好序后用指针从磁盘取出数据返回
  • 条件字段尽量命中索引
  • 加大 max_length_for_sort_data,在未命中索引的情况下使用第二中方式(以空间换时间)
    尽量减少不必要的返回字段
    增大 sort_buffer 减少排序过程对排序数据的分段

影响性能因素

  • 人为因素:需求
  • 程序员因素:面向对象思想影响解决问题方法
  • Cache:mysql的查询缓存
  • 对可扩展过度追求
  • 数据表的范式
  • 应用场景:OLTP/OLAP
  • 索引:数据结构和字段选取
  • SQL:SQL语句的效率

你可能感兴趣的:(mysql)