MySQL优化

MySQL 优化

前段时间公司内部做技术培训,轮到我需要讲MySQL的优化,基本上准备了一个周,看书看视频做了一个ppt。觉得里面有很多干货,
所以把ppt的内容整理到博客中。

数据类型的优化

更小的更好

使用更小的数据更小的数据类型会更快,对于CPU,磁盘和内存,io时间的消耗更少.但是要注意不要超出使用范围,对于MySQL而言,增加数据范围是非常麻烦的.需要去更新这个表.

简单就好

简单数据类型的操作通常需要更少的CPU周期。例如,整型比字符操作代价更低,因为字符集和校队规则(排序规则)使字符比较比整型比较更复杂。这里有两个例子:一个是应该使用MySQL内建的类型而不是字符串来存储日期和时间,另外一个是应该用整型存储IP地址。

尽量避免使用NULL

这一点是针对索引的,如果一个字段会使用索引,那么使用NULL后MySQL,引擎会更难优化.
当然也有例外对于使用innoDB引擎而言,使用单独的位(bit)来处理NULL,所以对于不需要建立索引的稀疏数据(大多数数据都是NULL)可以节约更多的磁盘空间

数据类型

数值类型.png
  • 根据我们上面的规则,选择小而简单的是最基本的原则.比如如果一个数据类型,它保存性别,那么使用tinyint是一个合理的选择

  • 对于整数类型而言,有无符号,具有相同的性能和存储空间,在都是自然数的情况下选择无符号是合理.

  • decimal类型存储的是精确小数,因此它支持MySQL服务器下的精确运算.但是对于CPU和绝大多数编程语言而言他们支持浮点型运算,会损失精度,但是浮点型的运算速度会更快.这一点需要权衡.使用decimal会需要额外的空间和运算速度.

  • int类型有时会指定一个长度。但是,这里的长度并非是NT类型存储的最大长度,而是显示的最大长度。并不会影响到使用,只会对MySQL工具显示造成影响

字符串类型.png
  • varchar是可变长的字符串,他比定长类型要节约空间.原因在于他只保存当然存在的长度,但是一旦发生update操作,varchar会需要长度变化,从而造成性能下降

  • 对于字符串长度变化大的,更新少的我们建议使用varchar

  • 相反char型适合存储很短的字符串,或者长度相近的字符串.比如使用hash算法生成的字符串(md5值, sha1密码)

  • 对于varchar而言使用更小的列会更为有效,更节约运算使用内存.因为对于MySQL引擎而言,他会倾向与使用固定大小的块来保持内部值,比如临时表.对于varchar(255)对比varchar(20),会在内部优化与计算中产生更大的内存.

  • BLOB系列存储二进制字符串,与字符集无关。TEXT系列存储非二进制字符串,与字符集相关。一般情况下,你可以认为BLOB是一个更大的VARBINARY;TEXT是一个更大的VARCHAR

  • BLOB和TEXT都不能有默认值,也无法建立索引(不是全文索引),只能针对前面一些字符建立索引

  • BLOB系列存储二进制字符串,与字符集无关。TEXT系列存储非二进制字符串,与字符集相关。一般情况下,你可以认为BLOB是一个更大的VARBINARY;TEXT是一个更大的VARCHAR

  • BINARY 和 VARBINARY 与 CHAR 和 VARCHAR 类似,但是他们保存的是二进制串,与字符集无关


    日期类型.png
  • 用MySQL的内建类型DATE、TIME、DATETIME来存储时间,而不是使用字符串

  • 相比较于datetime类型我们更推荐使用timestamp类型,他的空间效率更高,但要注意timestamp的范围更短

  • MySQL只支持秒级别的时间,如果需要微秒级别时间,可以使用BIGINT来保存,或者采用MariaDB代替MySQL

创建高性能索引

索引的基础

  • 索引是存储引擎快速找到记录的一种数据结构

  • 索引优化是对查询性能优化最有效的手段。
    [图片上传中...(B-Tree.png-a6bcbc-1583072196920-0)]

  • 索引的类型:B-Tree索引、哈希索引、空间索引、全文索引。

  • B-Tree索引:对索引列是顺序存储的适合键值范围、全键值和键前缀查找,其中键前缀查找只适用于最左前缀查找;

  • B-Tree索引的限制:如果不是按照索引最左列开始查找则无法使用索引,不能够跳过索引,如果查询中有某个列的范围查找则其他右边的所有列都无法使用索引优化查找。

  • 哈希索引:在MySQL中只有Memory引擎显式支持哈希索引,哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。

  • 哈希索引的限制:哈希索引只包含哈希值和行指针,哈希索引数据并不是按照索引值顺序存储的所以无法用于排序,哈希索引不支持部分索引列匹配查找哈希索引只支持等值比较查询,如果哈希冲突很多的话索引维护代价会很高,访问哈希索引当前出现哈希冲突的时候存储引擎必须遍历所有行的指针直到找到符合条件的行。

    B-Tree

    B-Tree.png

    B树的特点:

    1. 所有键值分布在整个树中
    2. 任何关键字出现且只出现在一个节点中
    3. 搜索有可能在非叶子节点结束
    4. 在关键字全集内做一次查找,性能逼近二分查找算法

    B+Tree

    B+Tree.png

从图中也可以看到,B+树与B树的不同在于:
1. 所有关键字存储在叶子节点,非叶子节点不存储真正的data
2. 为所有叶子节点增加了一个链指针

那么问题来了,为什么用B/B+树这种结构来实现索引呢??

答:红黑树等结构也可以用来实现索引,但是文件系统及数据库系统普遍使用B/B+树结构来实现索引。mysql是基于磁盘的数,
据库索引是以索引文件的形式存在于磁盘中的,索引的查找过程就会涉及到磁盘IO消耗,磁盘IO的消耗相比较于内存IO的消耗
要高好几个数量级,所以索引的组织结构要设计得在查找关键字时要尽量减少磁盘IO的次数。为什么要使用B/B+树,跟磁盘的
存储原理有关。

为什么mysql的索引使用B+树而不是B树呢??

  1. B+树更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储data,所以一个节点可以存储更多的内节点,
    每个节点能索引的范围更大更精确。也就是说使用B+树单次磁盘IO的信息量相比较B树更大,IO效率更高。
  2. mysql是关系型数据库,经常会按照区间来访问某个索引列,B+树的叶子节点间按顺序建立了链指针,加强了区间访
    问性,所以B+树对索引列上的区间范围查询很友好。而B树每个节点的key和data在一起,无法进行区间查找。

HASH索引

  • MySQL中只有Memory引擎显式支持哈希索引,

  • InnoDB引擎有一种特殊的功能叫“自适应哈希索引”,当InnoDB注意到某些索引值被引用得非常频繁时,它会在内存中基于B-Tree索引之上再创建一个哈希索引。这会让B-Tree索引也具有哈希索引的一些优点,比如快速查询。这是一个完全自主的、内部的行为,用户无法控制或者配置,不过可以关闭该功能。
    url创建url_crc哈希列:
    alter table table add column url_crc int unsigned NOT NULL default 0;
    然后为url_crc列加上索引:
    alter table table add index idx_url_crc(url_crc);
    查询时可以根据url和url_crc的值进行查询:
    select * from table where url='http://www.mysql.com' and url_crc=crc32("http://www.mysql.com");
    存储的时候加触发器将url 转为hash 也可以业务插入 使用crc32 做hash 算法因为比较短生成的数据

    注意事项

    1. HASH索引只用于使用 = 或 <=> 操作符的等式比较。如果一定要使用范围查询 的话,只能使用BTREE索引。
    2. 优化器不能使用 Hash 索引来加速 order by 操作。
    3. 使用 Hash 索引时 MySQL 不能确定在两个值之间大约有多少行。如果将一 个MyISAM表改为的 Hash 索引 memory 表,
      会影响一些查询的执行效率。
    4. Hash索引只能使用整个关键字来搜索一行。

空间索引(R-Tree)

  • MyIsam表支持空间索引,Mysql本身对GIS的支持并不完善,开源关系数据库中对GIS的解决方案做的比较好的是PostgreSQL的PostGIS

全文索引

  • 全文索引是一种特殊的索引,它查找的是文本中的关键词而不是直接比较索引中的值。全文索引更加类似与搜索引擎做的事情,而不是简单的WHERE匹配。在同一列上同时创建全文索引和基于值的B-Tree索引不会有冲突。没什么用,必须手动输入varchar,text中的空格,所以不支持中文分词,只是把所有空格隔开的单词进行了一下倒排序。

高性能索引策略

独立的列

  • 指索引不能是表达式的一部分,也不能是函数的参数。常见的非独立的列
    表达式的一部分,不是独立列
    select act_id from table where act_id+1 = 5;
    函数的参数
    select * from table where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col)<=10;

索引的选择性

不重复的值(Cardinality, 基数)占数据表总数的比值。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性是1,这时最好的索引选择性,性能也是最好的。选择索引时应该选择足够长的前缀以保证较高的选择性,同时又不能太长。

前缀索引

  • 有时候索引内容过长,这会使索引大且慢,解决方法一种是前面说的hash索引。还有什么别的办法呢

  • 通常我们可以索引这个列的开始部分字符,而对于blob,text很长的varchar类型必须使用前缀索引(如果需要加索引的话),因为mysql 存不了这么长的索引
    这样来选择前缀索引:
    select count(distinct city)/count(*) from sakila.city_demo

      select count(distinct left(city, 7))/count(*) as sel7 from sakila.city_demo
      
      alter table sakila.city_demo add key (city(7))
    

聚簇索引

  • 聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。InnoDB的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行(索引的顺序与数据的物理存放位置一致,“聚簇”表示数据行和相应的键值紧凑地存储在一起),因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。并不是所有的存储引擎都支持聚簇索引。
  • InnoDB通关过主键聚集数据。如果没有定义主键,InnoDB会选择一个唯一的非空索引作为替代。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引
  • innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值


    聚簇索引.png

索引覆盖

如果一个索引包含所有需要查询的字段值我就称为索引覆盖,覆盖索引能够极大的提高性能,覆盖索引带来的好处有:

  • 索引条目远小于数据行大小,能够极大地提高性能,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量
  • 因为索引是按照值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘中读取每一行数据的I/O要少的多。

使用索引扫描做排序
如果Explain出来的Type列为“index”, 则说明MySQL使用了索引扫描来做排序。扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引无法覆盖所有的列,那就不得不扫描一条索引记录就回表查询一次对应的行,这基本上属于随机I/O,因此按索引顺序读取数据的速度通常比顺序地全表扫描要慢。

执行计划

执行计划字段

图片1.png

其中最重要的字段为:id、type、key、rows、Extra

各字段详解

id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:

  1. id相同:执行顺序由上至下

    图片2.png

  2. id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行


    图片3.png
  3. id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

    图片4.png

select_type

查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询

  1. SIMPLE:简单的select查询,查询中不包含子查询或者union
  2. PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary
  3. SUBQUERY:在select 或 where列表中包含了子查询
  4. DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在零时表里
  5. UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived
  6. UNION RESULT:从union表获取结果的select
    图片5.png

type

  • 访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:
    system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
  • 一般来说,好的sql查询至少达到range级别,最好能达到ref
  1. system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计

  2. const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const


    图片6.png
  3. eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描


    图片7.png
  4. ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体


    图片8.png
  5. range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引


    图片9.png
  6. index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,因为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)


    图片10.png
  7. ALL:Full Table Scan,遍历全表以找到匹配的行


    图片11.png

possible_keys

查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用

key

实际使用的索引,如果为NULL,则没有使用索引。
查询中如果使用了覆盖索引,则该索引仅出现在key列表中

key_len

表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。通过key_len可以检验索引是否生效

ref

显示索引的那一列被使用了,如果可能,是一个常量const。

rows

通过索引读取的行数

Extra

不适合在其他字段中显示,但是十分重要的额外信息

  1. Using filesort :
    mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”


    图片12.png

由于索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了,mysql内部必须再实现一次“文件排序”

  1. Using temporary:
    使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by

    图片13.png

  2. Using index:
    表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
    如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)
    如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作


    图片14.png

覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
注意:

  • 如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select *
  • 如果将所有字段都建索引会导致索引文件过大,反而降低crud性能
  1. Using where :
    使用了where过滤
  2. Using join buffer :
    使用了链接缓存
  3. Impossible WHERE:
    where子句的值总是false,不能用来获取任何元祖
    图片15.png

你可能感兴趣的:(MySQL优化)