MySql优化之索引原理与 SQL 优化

1.Sql性能不高缘由

  • 查询数据过多(能不能拆,条件过滤尽可能少)
  • 关联了太多的表,太多join (join 原理。用 A 表的每一条数据 扫描 B表的全部数据。因此尽可能先过滤。)
  • 没有利用到索引
  • 服务器调优及各个参数设置(缓冲、线程数等)

2.Sql编写顺序与执行顺序

  • 编写顺序
select distinct  '字段'   
from '表'   
join '表' on '条件'   
where '条件'   
group by '字段'   
having '条件'   
order by '字段'   
limit '条件'
复制代码
  • 执行顺序
from '表'   
on '条件'
'join类型' join '表'
where '条件'
group by '字段'
having '条件'
select distinct '字段'
order by '字段'
limit '条件'
复制代码
  • Sql解析图

3.索引

3.1什么是索引

索引(Index)是帮助MySQL高效获取数据的数据结构。在数据以外,数据库系统还维护着知足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就能够在这些数据结构上实现高效的查找算法.这种数据结构,就是 索引.
通常来讲索引自己也很大,不可能所有存储在内存中,所以每每以索引文件的形式存放在磁盘中.咱们日常所说的索引,若是没有特别说明都是指BTree索引(平衡多路搜索树).其中汇集索引,次要索引,覆盖索引复合索引,前缀索引,惟一索引默认都是使用的BTree索引,统称索引. 除了BTree索引以后,还有哈希索引mysql

3.2索引原理

3.2.1说明

  • 索引(Index)是帮助MySQL高效获取数据的数据结构。不一样的引擎使用的数据结构也不尽相同.
  • MyISAM采起的是BTree, InnoDB采起的是B+TREE
    咱们工做的里面的数据库通常用的是InnoDB,因此咱们重点来说解B+TREE.B+树索引是B+树在数据库中的一种实现,是最多见也是数据库中使用最为频繁的一种索引。B+树中的B表明平衡(balance),而不是二叉(binary),由于B+树是从最先的平衡二叉树演化而来的。在讲B+树以前必须先了解二叉查找树、平衡二叉树(AVLTree)和平衡多路查找树(B-Tree),B+树即由这些树逐步优化而来。

3.2.2二叉查找树

二叉树具备如下性质:左子树的键值小于根的键值,右子树的键值大于根的键值。 以下图所示就是一棵二叉查找树算法

对该二叉树的节点进行查找发现深度为1的节点的查找次数为1,深度为2的查找次数为2,深度为n的节点的查找次 数为n,所以其平均查找次数为 (1+2+2+3+3+3) / 6 = 2.3次

二叉查找树能够任意地构造,一样是2,3,5,6,7,8这六个数字,也能够按照下图的方式来构造:sql

可是这棵二叉树的查询效率就低了。所以若想二叉树的查询效率尽量高,须要这棵二叉树是平衡的,从而引出新 的定义——平衡二叉树,或称`AVL`树。

3.2.3平衡二叉树(AVL Tree)

平衡二叉树(AVL树)在符合二叉查找树的条件下,还知足任何节点的两个子树的高度最大差为1。下面的两张 图片,左边是AVL树,它的任何节点的两个子树的高度差<=1;右边的不是AVL树,其根节点的左子树高度为3,而 右子树高度为1;数据库

若是在AVL树中进行插入或删除节点,可能致使AVL树失去平衡,这种失去平衡的二叉树能够归纳为四种姿态: LL(左左)、RR(右右)、LR(左右)、RL(右左)。它们的示意图以下:

这四种失去平衡的姿态都有各自的定义:缓存

LL:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非 空节点,致使根节点的左子树高度比右子树高度高2,AVL树失去平衡。
RR:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child) 还有非空节点,致使根节点的右子树高度比左子树高度高2,AVL树失去平衡。
LR:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有 非空节点,致使根节点的左子树高度比右子树高度高2,AVL树失去平衡。
RL:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有 非空节点,致使根节点的右子树高度比左子树高度高2,AVL树失去平衡。服务器

AVL树失去平衡以后,能够经过旋转使其恢复平衡。下面分别介绍四种失去平衡的状况下对应的旋转方法。 LL的旋转。LL失去平衡的状况下,能够经过一次旋转让AVL树恢复平衡。步骤以下:数据结构

  1. 将根节点的左孩子做为新根节点。
  2. 将新根节点的右孩子做为原根节点的左孩子。
  3. 将原根节点做为新根节点的右孩子。

LL旋转示意图以下:并发

RR的旋转:RR失去平衡的状况下,旋转方法与LL旋转对称,步骤以下:函数

  1. 将根节点的右孩子做为新根节点。
  2. 将新根节点的左孩子做为原根节点的右孩子。
  3. 将原根节点做为新根节点的左孩子。

RR旋转示意图以下:高并发

LR的旋转:LR失去平衡的状况下,须要进行两次旋转,步骤以下:

  1. 围绕根节点的左孩子进行RR旋转。
  2. 围绕根节点进行LL旋转。

LR的旋转示意图以下:

RL的旋转:RL失去平衡的状况下也须要进行两次旋转,旋转方法与LR旋转对称,步骤以下:

  1. 围绕根节点的右孩子进行LL旋转。
  2. 围绕根节点进行RR旋转。

RL的旋转示意图以下:

3.2.4平衡多路查找树(B-Tree)

B-Tree是为磁盘等外存储设备设计的一种平衡查找树。所以在讲B-Tree以前先了解下磁盘的相关知识。

系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出 来,而不是须要什么取什么。

InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每一个页的大小为 16KB,可经过参数innodb_page_size将页的大小设置为4K、8K、16K.

而系统一个磁盘块的存储空间每每没有这么大,所以InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到 页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时若是一个页中的每条数据都 能有助于定位数据记录的位置,这将会减小磁盘I/O次数,提升查询效率。

一棵m阶的B-Tree有以下特性:

  1. 每一个节点最多有m个孩子。
  2. 除了根节点和叶子节点外,其它每一个节点至少有Ceil(m/2)个孩子。
  3. 若根节点不是叶子节点,则至少有2个孩子
  4. 全部叶子节点都在同一层,且不包含其它关键字信息
  5. 每一个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  6. 关键字的个数n知足:ceil(m/2)-1 <= n <= m-1
  7. ki(i=1,…n)为关键字,且关键字升序排序。
  8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的全部节点关键字均小于ki,但都大于k(i-1)

B-Tree中的每一个节点根据实际状况能够包含大量的关键字信息和分支,以下图所示为一个3阶的B-Tree:

每一个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储 的是子节点所在磁盘块的地址。两个关键词划分红的三个范围域对应三个指针指向的子树的数据的范围域。以根节 点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3 指针指向的子树的数据范围为大于35。

模拟查找关键字29的过程:

  1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操做第1次】
  2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
  3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操做第2次】
  4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
  5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操做第3次】
  6. 在磁盘块8中的关键字列表中找到关键字29。

分析上面过程,发现须要3次磁盘I/O操做,和3次内存查找操做。因为内存中的关键字是一个有序表结构,能够利用 二分法查找提升效率。而3次磁盘I/O操做是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节 点个数,使每次磁盘I/O取到内存的数据都发挥了做用,从而提升了查询效率。

3.2.5B+Tree

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索 引结构。

从上一节中的B-Tree结构图中能够看到每一个节点中不只包含数据的key值,还有data值。而每个页的存储空间是 有限的,若是data数据较大时将会致使每一个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同 样会致使B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,全部数据记录节点都 是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样能够大大加大每一个节点 存储的key值数量,下降B+Tree的高度。

B+Tree相对于B-Tree有几点不一样:

  1. 非叶子节点只存储键值信息。
  2. 全部叶子节点之间都有一个链指针。
  3. 数据记录都存放在叶子节点中。

将上一节中的B-Tree优化,因为B+Tree的非叶子节点只存储键值信息,假设每一个磁盘块能存储4个键值及指针信 息,则变成B+Tree后其结构以下图所示:

一般在B+Tree上有两个头指针,一个指向根节点,另外一个指向关键字最小的叶子节点,并且全部叶子节点(即数据 节点)之间是一种链式环结构。所以能够对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另 一种是从根节点开始,进行随机查找。

可能上面例子中只有22条数据记录,看不出B+Tree的优势,下面作一个推算:

InnoDB存储引擎中页的大小为16KB,通常表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针 类型也通常为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(由于 是估值,为方便计算,这里的K取值为〖10〗^3)。也就是说一个深度为3的B+Tree索引能够维护10^3 * 10^3 * 10^3 = 10亿 条记录。

实际状况中每一个节点可能不能填充满,所以在数据库中,B+Tree的高度通常都在2~4层。mysql的InnoDB存储引擎 在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只须要1~3次磁盘I/O操做。

数据库中的B+Tree索引能够分为汇集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示 例图在数据库中的实现即为汇集索引,汇集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与 汇集索引的区别在于辅助索引的叶子节点并不包含行记录的所有数据,而是存储相应行数据的汇集索引键,即主 键。当经过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,而后再经过主键在汇集索引中找到 完整的行记录数据

3.3索引的优缺点

  • 优势:
    提升数据查询的效率,下降数据库的IO成本
    经过索引对数据进行排序,下降数据排序的成本,下降CPU的消耗
  • 缺点:
    索引自己也是一张表,该表保存了主键与索引字段,并指向实体表的记录,因此索引列也要占用空间
    虽然索引大大提升了查询的速度,同时反向影响增删改操做的效率,由于表中数据变化以后,会致使索引内容不许, 因此也须要更新索引表信息,增长了数据库的工做量
    随着业务的不断变化,以前创建的索引可能不能知足查询需求,须要消耗咱们的时间去更新索引

3.4索引的类别

3.4.1普通索引

是最基本的索引,它没有任何限制.

CREATE index 索引名 on 表名(列名)
复制代码

3.4.2惟一索引

与前面的普通索引相似,不一样的就是:索引列的值必须惟一,但容许有空值。若是是组合索引,则列值的组合必须 惟一。

CREATE UNIQUE index 索引名 on 表名(列名)
复制代码

3.4.3主键索引

是一种特殊的惟一索引,一个表只能有一个主键,不容许有空值。通常是在建表的时候同时建立主键索引.也就 是说主键约束默认索引

3.4.4复合索引

指多个字段上建立的索引,只有在查询条件中使用了建立索引时的第一个字段,索引才会被使用。使用组合索引时 遵循最左前缀集合

CREATE index 索引名 on 表名(列名,列名...)
复制代码

3.4.5全文索引

主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个 搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操做使用,而不是通常的where 语句加like。它能够在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上能够 建立全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,而后再用CREATE index建立fulltext索引,要比先为一张表创建fulltext而后再将数据写入的速度快不少

CREATE TABLE `table` ( 
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`), FULLTEXT (content) 
);
复制代码

3.5索引的基本语法

  • 建立
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON 表名(列名)
复制代码
  • 删除
DROP INDEX [indexName] ON 表名;
复制代码
  • 查看
SHOW INDEX FROM 表名
复制代码
  • alter命令
‐‐ 有四种方式来添加数据表的索引: 
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是惟一的, 且不能为NULLALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句建立索引的值必须是惟一的(除了NULL 外,NULL可能会出现屡次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现屡次。 
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
复制代码

3.6索引的使用场景

3.6.1适合使用索引

  1. 频繁做为查询条件的字段应该建立索引
  2. 多表查询中与其它表进行关联的字段,外键关系创建索引
  3. 单列索引/复合索引的选择,高并发下倾向于建立复合索引
  4. 查询中常常用来排序的字段
  5. 查询中常常用来统计或者分组字段

3.6.2不适合使用索引

  1. 频繁更新的字段: 每次更新都会影响索引树
  2. where条件查询中用不到的字段
  3. 表记录太少
  4. 常常增删改的表: 更新了表,索引也得更新才行
  5. 注意: 若是一张表中,重复的记录很是多,为它创建索引就没有太大意义

4.性能分析

4.1Query Optimizer

MySQL Optimizer是一个专门负责优化SELECT 语句的优化器模块,它主要的功能就是经过计算分析系统中收 集的各类统计信息,为客户端请求的Query 给出他认为最优的执行计划,也就是他认为最优的数据检索方式。

4.2MySQL常见瓶颈

  1. CPU饱和:CPU饱和的时候,通常发生在数据装入内存或从磁盘上读取数据的时候
  2. IO瓶颈: 磁盘IO瓶颈发生在装入数据远大于内存容量的时候
  3. 服务器硬件的性能瓶颈

4.3执行计划Explai

4.3.1Explain概述

使用explain关键字能够模拟优化器执行SQL查询语句,从而知道MYSQL是如何处理SQL语句的.咱们能够用执行 计划来分析查询语句或者表结构的性能瓶颈

4.3.2Explain做用

  1. 查看表的读取顺序
  2. 查看数据库读取操做的操做类型
  3. 查看哪些索引有可能被用到
  4. 查看哪些索引真正被用到
  5. 查看表之间的引用
  6. 查看表中有多少行记录被优化器查询

4.3.3语法

  • 语法
explain sql语句
复制代码

4.3.4各字段解释

  • 准备工做
create table t1(
id int primary key, 
name varchar(20), 
col1 varchar(20), 
col2 varchar(20), 
col3 varchar(20) 
);
create table t2( 
id int primary key,
name varchar(20), 
col1 varchar(20), 
col2 varchar(20), 
col3 varchar(20) 
);
create table t3( 
id int primary key,
name varchar(20),
col1 varchar(20),
col2 varchar(20), 
col3 varchar(20) 
);
insert into t1 values(1,'zs1','col1','col2','col3'); 
insert into t2 values(1,'zs2','col2','col2','col3'); 
insert into t3 values(1,'zs3','col3','col2','col3'); 
create index ind_t1_c1 on t1(col1);
create index ind_t2_c1 on t2(col1); 
create index ind_t3_c1 on t3(col1);
create index ind_t1_c12 on t1(col1,col2);
create index ind_t2_c12 on t2(col1,col2); 
create index ind_t3_c12 on t3(col1,col2);
复制代码
  • 执行explain sql语句后:

4.3.4.1 id

  • select 查询的序列号,包含一组数字,表示查询中执行Select子句或操做表的顺序

  • 三种状况:

    1. id值相同,执行顺序由上而下
    explain select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id= t3.id and t1.name = 'zs';
    复制代码
    2. id值不一样,id值越大优先级越高,越先被执行
    explain select t2.* from t2 where id = (select id from t1 where id = (select t3.id from t3 where t3.name='zs3'));
    复制代码
    3. id值有相同的也有不一样的,若是id相同,从上往下执行,id值越大,优先级越高,越先执行
    explain select t2.* from (select t3.id from t3 where t3.name='zs3') s1,t2 where s1.id = t2.id;
    复制代码

4.3.4.2select_type

查询类型,主要用于区别

  • SIMPLE : 简单的select查询,查询中不包含子查询或者UNION
  • PRIMARY: 查询中若包含复杂的子查询,最外层的查询则标记为PRIMARY
  • SUBQUERY : 在SELECT或者WHERE列表中包含子查询
  • DERIVED : 在from列表中包含子查询被标记为DRIVED衍生,MYSQL会递归执行这些子查询,把结果放到临时表 中
  • UNION: 若第二个SELECT出如今union以后,则被标记为UNION, 若union包含在from子句的子查询中,外层 select被标记为:derived
  • UNION RESULT: 从union表获取结果的select
explain select col1,col2 from t1 union select col1,col2 from t2;
复制代码

4.3.4.3table

显示这一行的数据是和哪张表相关

4.3.4.4type

访问类型: all, index,range,ref,eq_ref, const,system,null
最好到最差依次是: system > const > eq_ref>ref >range > index > all , 最好能优化到range级别或则ref级别

  • system: 表中只有一行记录(系统表), 这是const类型的特例, 基本上不会出现
  • const: 经过索引一次查询就找到了,const用于比较primary key或者unique索引,由于只匹配一行数据,因此很 快,如将主键置于where列表中,mysql就会将该查询转换为一个常量
explain select * from (select * from t1 where id=1) s1;
复制代码
  • eq_ref: 惟一性索引扫描, 对于每一个索引键,表中只有一条记录与之匹配, 常见于主键或者惟一索引扫描
explain select * from t1,t2 where t1.id = t2.id;
复制代码
  • ref : 非惟一性索引扫描,返回匹配某个单独值的全部行,本质上也是一种索引访问,它返回全部符合条件的行,然而 它可能返回多个符合条件的行
explain select * from t1 where col1='zs1';
复制代码
  • range : 只检索给定范围的行, 使用一个索引来选择行.key列显示的是真正使用了哪一个索引,通常就是在where条 件中使用between,>,<,in 等范围的条件,这种在索引范围内的扫描比全表扫描要好,由于它只在某个范围中扫描, 不须要扫描所有的索引
explain select * from t1 where id between 1 and 10;
复制代码
  • index : 扫描整个索引表, index 和all的区别为index类型只遍历索引树. 这一般比all快,由于索引文件一般比数据 文件小,虽然index和all都是读全表,可是index是从索引中读取,而all是从硬盘中读取数据
explain select id from t1;
复制代码
  • all : full table scan全表扫描 ,将遍历全表以找到匹配的行
explain select * from t1;
复制代码
  • 注意: 开发中,咱们得保证查询至少达到range级别,最好能达到ref. 若是百万条数据出现all, 通常状况下就须要考虑使用索引优化了

4.3.4.5possible_keys

SQL查询中可能用到的索引,但查询的过程当中不必定真正使用

4.3.4.6key

查询过程当中真正使用的索引,若是为null,则表示没有使用索引
查询中使用了覆盖索引,则该索引仅出如今key列表中

explain select t2.* from t1,t2,t3 where t1.col1 = ' ' and t1.id = t2.id and t1.id= t3.id;
复制代码
explain select col1 from t1;
复制代码

4.3.4.7key_len

索引中使用的字节数,可经过该列计算查询中使用的索引的长度,在不损失精确度的状况下,长度越短越好, key_len显 示的值为索引字段的最大可能长度,并不是实际使用长度, 即key_len是根据表定义计算而得

explain select * from t1 where col1='c1';
复制代码
explain select * from t1 where col1='col1' and col2 = 'col2';
‐‐ 注意: 为了演示这个结果,咱们删除了c1上面的索引 
alter table t1 drop index ind_t1_c1; 
‐‐ 执行完成以后,再次建立索引 
create index ind_t1_c1 on t1(col1);
复制代码

4.3.4.8ref

显示索引的哪一列被使用了,若是可能的话,是一个常数.哪些列或者常量被用于查找索引列上的值

explain select * from t1,t2 where t1.col1 = t2.col1 and t1.col2 = 'col2';
复制代码

4.3.4.9rows

根据表统计信息及索引选用的状况,估算找出所需记录要读取的行数 (有多少行记录被优化器读取) ,越少越好

4.3.4.10extra

包含其它一些很是重要的额外信息

  • Using filesort : 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,Mysql中无 法利用索引完成的排序操做称为文件排序
explain select col1 from t1 where col1='col1' order by col3;
复制代码
‐‐ 上面这条SQL语句出现了using filesort,可是咱们去执行下面这条SQL语句的时候它,又不会出现using filesort
explain select col1 from t1 where col1='col1' order by col2;
复制代码
‐‐ 如何优化第一条SQL语句 ? 
create index ind_t1_c13 on t1(col1,col3);
explain select col1 from t1 where col1='col1' order by col3;
复制代码
  • Using temporary : 使用了临时表保存中间结果,Mysql在对查询结果排序时使用了临时表,常见于order by 和分 组查询group by
explain select col1 from t1 where col1>'col1' group by col2;
复制代码
explain select col1 from t1 where col1 >'col1' group by col1,col2;
复制代码
  • Using index :
  • 查询操做中使用了覆盖索引(查询的列和索引列一致),避免访问了表的数据行,效率好
  • 若是同时出现了using where, 代表索引被用来执行索引键值的查找
  • 若是没有同时出现using where, 代表索引用来读取数据而非执行查找动做
  • 覆盖索引: 查询的列和索引列一致, 换句话说查询的列要被所键的索引覆盖,就是select中数据列只需从索引中就 能读取,没必要读取原来的数据行,MySql能够利用索引返回select列表中的字段,而没必要根据索引再次读取数据文件
explain select col2 from t1 where col1='col1';
复制代码
explain select col2 from t1;
复制代码
  • using where : 代表使用了where条件过滤
  • using join buffer : 代表使用了链接缓存, join次数太多了可能会出现
  • impossible where : where子句中的值老是false,不能用来获取任何数据
explain select * from t1 where col1='zs' and col1='ls';
复制代码
  • select tables optimized away :
  • 在没有group by 子句的状况下, 基于索引优化min/max操做或者对于MyISAM存储引擎优化count(*)操做,没必要 等到执行阶段再进行计算,查询执行计划生成阶段即完成优化
  • distinct : 优化distinct操做,在找到第一个匹配的数据后即中止查找一样的值的动做

5.优化实战

5.1单表查询优化

需求: 查询 category_id 为1 且 comments 大于 1 的状况下,views 最多的 article_id。

  • 建表语句
CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL, 
`category_id` INT(10) UNSIGNED NOT NULL, 
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL 
);
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');
复制代码
  • 执行计划
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
复制代码
  • 没有索引状况
结论:很显然,type 是 ALL,即最坏的状况。Extra 里还出现了 Using filesort,也是最坏的状况。优化是必须的。
  • 优化一:
create index idx_article_ccv on article(category_id,comments,views);
复制代码

结论: type 变成了 range,这是能够忍受的。可是 extra 里使用 Using filesort 还是没法接受的。可是咱们已经创建 了索引,为啥没用呢? 这是由于按照 BTree 索引的工做原理, 先排序 category_id, 若是遇到相同的 category_id 则再 排序 comments,若是遇到相同的 comments 则再排序 views。当 comments 字段在联合索引里处于中间位置时,因 comments > 1 条件是一个范围值(所谓 range),MySQL 没法利用索引再对后面的 views 部分进行检索,即 range 类 型查询字段后面的索引无效。

  • 优化二:
‐‐ 先删除优化一索引 
DROP INDEX idx_article_ccv ON article;
‐‐ 建立索引 
create index idx_article_cv on article(category_id,views);
复制代码

5.2关联查询优化

5.2.1示例

需求: 使用左外链接查询class和book

  • 建表语句
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`) 
);
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`) 
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20))); INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
复制代码
  • 执行计划
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
复制代码
  • 优化
ALTER TABLE `book` ADD INDEX Y ( `card`);
复制代码

能够看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
这是由左链接特性决定的。LEFT JOIN 条件用于肯定如何从右表搜索行,左边必定都有,因此右边是咱们的关键点,必定 须要创建索引。
也就是说: 外链接在相反方建立索引

5.2.2优化建议

  • 保证被驱动表的join字段已经被索引 (被驱动表 join 后的表为被驱动表 (须要被查询))
  • left join 时,选择小表做为驱动表,大表做为被驱动表。
  • inner join 时,mysql会本身帮你把小结果集的表选为驱动表。

5.3子查询优化

  • 子查询尽可能不要放在被驱动表,有可能使用不到索引。
select a.name ,bc.name from t_emp a left join 
    (select b.id , c.name from t_dept b
    inner join t_emp c on b.ceo = c.id)bc
    on bc.id = a.deptid 
上段查询中用到了子查询,必然 bc 表没有索引。确定会进行全表扫描
上段查询 能够直接使用 两个 left join 优化 
select a.name , c.name from t_emp a 
    left outer join t_dept b on a.deptid = b.id 
    left outer join t_emp c on b.ceo=c.id 
全部条件均可以使用到索引 
若必须用到子查询,可将子查询设置为驱动表,由于驱动表的type 确定是 all,而子查询返回的结果表没有索引,一定 也是all
复制代码

5.4order by优化

5.4.1环境准备

  • 建表语句
CREATE TABLE tblA(
id int primary key not null auto_increment, 
age INT,
birth TIMESTAMP NOT NULL,
name varchar(200) 
);

INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd'); 
INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');

CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
复制代码

5.4.2说明

MySQL支持二种方式的排序,FileSort和Index,Index效率高. 它指MySQL扫描索引自己完成排序。FileSort方式效 率较低。

5.4.3ORDER BY会使用Index方式排序

  • ORDER BY 语句使用索引最左前列
EXPLAIN SELECT * FROM tbla WHERE age > 1 ORDER BY age
复制代码
  • 使用Where子句与Order BY子句条件列组合知足索引最左前列
EXPLAIN SELECT * FROM tbla WHERE age = 1 ORDER BY birth
复制代码
  • where子句中若是出现索引的范围查询(即explain中出现range)会致使order by 索引失效
  • Order by 复合条件排序, 按照索引顺序, 先后的排序类别不一致,会致使order by 索引失效

5.5GROUP BY优化

5.5.1说明

group by实质是先排序后进行分组,遵守索引建的最佳左前缀

5.5.2注意

where高于having,能写在where限定的条件就不要去having限定了

5.6limit优化

5.6.1说明

limit经常使用于分页处理,时常会伴随order by 从句使用, 所以大多时候会使用Filesorts,这样会形成大量的IO问题

5.6.2优化

  • 步骤一: 使用有索引的列或者主键进行Order By操做
  • 步骤二: 记录上次返回的主键,在下次查询的时候使用注解过滤(若是主键不是连续的,是字符串类型,能够建立一 个列记录)

6.总结

  • 全值匹配我最爱
  • 最佳左前缀法则(若是索引了多列,要遵照最左前缀法则。指的是查询从索引的最左前列开始而且不跳过索引 中的列)
  • 不在索引列上作任何操做(计算、函数、(自动or手动)类型转换),会致使索引失效而转向全表扫描
  • 存储引擎不能使用索引中范围条件右边的列
  • 尽可能使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减小select *
  • mysql 在使用不等于(!= 或者<>)的时候没法使用索引会致使全表扫描
  • is not null 也没法使用索引,可是is null是可使用索引的
  • like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操做
  • 字符串不加单引号索引失效
  • 少用or,用它来链接时会索引失效
相关文章
相关标签/搜索