MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】

目录

 

一、存储引擎

MyISAM存储引擎

InnoDB存储引擎

二、索引

2.1 索引方法

2.2 索引类型 

1.普通索引

2.唯一索引

3.主键索引

4.组合索引

2.3 聚合索引和辅助索引 

2.4 正确使用索引

2.5 组合索引

2.6 注意事项

三、慢查询优化

3.1 查询计划

3.2 慢日志查询

3.3 大数据量分页优化 


 

MySQL数据库的体系架构如下图所示:

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第1张图片

从上图中可以看出,MySQL主要分为以下几个组件:

  • 连接池组件
  • 管理服务和工具组件
  • SQL接口组件
  • 分析器组件
  • 优化器组件
  • 缓冲组件
  • 插件式存储引擎
  • 物理文件 

 

一、存储引擎

存储引擎 : 其实就是指定  如何存储数据,如何为存储的数据 建立索引 以及 如何更新查询数据等技术实现的方法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型)

了解: 在Oracle 和SQL Server等数据库中只有一种存储引擎,所有数据存储管理机制都是一样的。而MySql数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第2张图片

其中最常见的两种存储引擎是MyISAM 和 InnoDB

 

MyISAM存储引擎

1、MyISAM 是MySQL (mysql 5.5版本以前) 原来的默认存储引擎.

2、MyISAM   这种存储引擎不支持事务,不支持行级锁,只支持并发插入的表锁。

3、MyISAM   类型的表支持三种不同的存储结构:静态型、动态型、压缩型。

4、MyISAM也是使用B+tree索引但是和Innodb的在具体实现上有些不同。

InnoDB存储引擎

(1)MySQL默认存储引擎(MySQL 5.5 版本后).

(2)innodb 支持事务,回滚以及系统崩溃修复能力和多版本迸发控制的事务的安全。

(3)innodb 支持自增长列(auto_increment),自增长列的值不能为空,(一个表只允许存在一个自增,并且要求自增列必须为索引)

(4)innodb 支持外键(foreign key) ,外键所在的表称为子表,而所依赖的表称为父表。

(5)innodb存储引擎支持行级锁。

(6)innodb存储引擎索引使用的是B+Tree

补充3点:

    1.大容量的数据集时趋向于选择Innodb。因为它支持事务处理和故障的恢复。Innodb可以利用数据日志来进行数据的恢复。主键的查询在Innodb也是比较快的。

    2.大批量的插入语句时(这里是INSERT语句)在MyIASM引擎中执行的比较的快,但是UPDATE语句在Innodb下执行的会比较的快,尤其是在并发量大的时候。

    3.两种引擎所使用的索引数据结构是什么?

        答案:都是B+树!

重点[面试题]:

innodb与MyIASM存储引擎的区别:
  1.innodb 是mysql5.5版本以后的默认存储引擎, 而MyISAM是5.5版本以前的默认存储引擎.
  2.innodb 支持事物,而MyISAM不支持事物
  3.innodb 支持行级锁.而MyIASM 它支持的是并发的表级锁.
  4.innodb 支持外键, 而MyIASM 不支持外键
  5.innodb与MyIASM存储引擎都采用B+TREE存储数据, 但是innodb的索引与数据存储在一个文件中,这种方式我们称之为聚合索引. 
    而MyIASM则会单独创建一个索引文件,也就是说,数据与索引是分离开的
  6.在效率方面MyISAM比innodb高,但是在性能方面innodb要好一点.

 

二、索引

2.1 索引方法

1. B+TREE 索引

  B+树是一种经典的数据结构,由平衡树二叉查找树结合产生,它是为磁盘或其它直接存取辅助设备而设计的一种平衡查找树,在B+树中,所有的记录节点都是按键值大小顺序存放在同一层的叶节点中,叶节点间用指针相连,构成双向循环链表,非叶节点(根节点、枝节点)只存放键值,不存放实际数据。下面看一个2层B+树的

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第3张图片

强烈注意: 索引字段要尽量的小,磁盘块可以存储更多的索引.

 2. HASH 索引

    hash就是一种(key=>value)形式的键值对,允许多个key对应相同的value,但不允许一个key对应多个value,为某一列或几列建立hash索引,就会利用这一列或几列的值通过一定的算法计算出一个hash值,对应一行或几行数据.   hash索引可以一次定位,不需要像树形索引那样逐层查找,因此具有极高的效率.

假设索引使用hash函数f( ),如下:

f('Arjen') = 2323
f('Baron') = 7437
f('Peter') = 8784
f('Vadim') = 2458

此时,索引的结构大概如下:

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第4张图片

3.HASH与BTREE比较:

hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量越大,范围查询和随机查询快(innodb默认索引类型)

不同的存储引擎支持的索引类型也不一样
InnoDB 支持事务,支持行级别锁定,支持 Btree、Hash 等索引
MyISAM 不支持事务,支持表级别锁定,支持 Btree、Full-text 等索引
Memory 不支持事务,支持表级别锁定,支持 Btree、Hash 等索引,
NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 Btree
Archive 不支持事务,支持表级别锁定,不支持 Btree、Hash

 

2.2 索引类型 

MySQL中常见索引有:

  • 普通索引
  • 唯一索引
  • 主键索引
  • 组合索引

1.普通索引

普通索引仅有一个功能:加速查询

代码创建表+索引:

-- 创建表同时添加name字段为普通索引
create table tb1(
   id int not null auto_increment primary key,
   name varchar(100) not null,
   index idx_name(name)  
);

创建索引

-- 单独为表指定普通索引

create index idx_name on tb1(name);

删除索引

drop index idx_name on tb1;

查看索引

show index from tb1;

查看索引的列表中,所有参数的意思:

1、Table 表的名称。

2、 Non_unique 如果索引为唯一索引,则为0,如果可以则为1。

3、 Key_name 索引的名称

4、 Seq_in_index 索引中的列序列号,从1开始。

5、 Column_name 列名称。

6、 Collation 列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。

7、Cardinality 索引中唯一值的数目的估计值。

8、Sub_part 如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。

9、 Packed 指示关键字如何被压缩。如果没有被压缩,则为NULL。

10、 Null 如果列含有NULL,则含有YES。如果没有,则该列含有NO。

11、 Index_type 用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。

12、 Comment 多种评注

 

2.唯一索引

唯一索引有两个功能:加速查询 和 唯一约束(可含一个null 值)

创建表+唯一(unique)索引

create table tb2(
  id int not null auto_increment primary key,
  name varchar(50) not null,
  age int not null,
  unique index idx_age (age)   
)

创建unique索引

create unique index idx_age on tb2(age);

 

3.主键索引

主键有两个功能:加速查询 和 唯一约束(不可含null)

 注意:一个表中最多只能有一个主键索引

创建表 + 创建主键:

-- create table tb3(
   id int not null auto_increment primary key,
   name varchar(50) not null,
   age int default 0 
);

-- 方式二:
create table tb3(
   id int not null auto_increment,
   name varchar(50) not null,
   age int default 0 ,
   primary key(id)
);

 创建主键

alter table tb3 add primary key(id);

删除主键

#方式一
alter table tb3 drop primary key;

#方式二:
#如果当前主键为自增主键,则不能直接删除.需要先修改自增属性,再删除

alter table tb3 modify id int ,drop primary key;

 

4.组合索引

组合索引是将n个列组合成一个索引

其应用场景为:频繁的同时使用n列来进行查询,如:where n1 = 'alex' and n2 = 666。

创建表+组合索引

create table tb4(
  id int not null ,
  name varchar(50) not null,
  age int not null,
  index idx_name_age (name,age)   
)

创建组合索引

create index idx_name_age on tb4(name,age);

 

2.3 聚合索引和辅助索引 

数据库中的B+树索引可以分为聚集索引和辅助索引.

聚集索引:InnoDB表 索引组织表,即表中数据按主键B+树存放,叶子节点直接存放整条数据,每张表只能有一个聚集索引。

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第5张图片

1.当你定义一个主键时,InnnodDB存储引擎则把它当做聚集索引

2.如果你没有定义一个主键,则InnoDB定位到第一个唯一索引,且该索引的所有列值均飞空的,则将其当做聚集索引。

3如果表没有主键或合适的唯一索引INNODB会产生一个隐藏的行ID值6字节的行ID聚集索引,

补充:由于实际的数据页只能按照一颗B+树进行排序,因此每张表只能有一个聚集索引,聚集索引对于主键的排序和范围查找非常有利.

辅助索引:(也称非聚集索引)是指叶节点不包含行的全部数据,叶节点除了包含键值之外,还包含一个书签连接,通过该书签再去找相应的行数据。

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第6张图片

InnoDB存储引擎辅助索引获得数据的查找方式:

从上图中可以看出,辅助索引叶节点存放的是主键值,获得主键值后,再从聚集索引中查找整行数据。举个例子,如果在一颗高度为3的辅助索引中查找数据,首先从辅助索引中获得主键值(3次IO),接着从高度为3的聚集索引中查找以获得整行数据(3次IO),总共需6次IO。一个表上可以存在多个辅助索引。

总结二者区别:

  相同的是:不管是聚集索引还是辅助索引,其内部都是B+树的形式,即高度是平衡的,叶子结点存放着所有的数据。

  不同的是:聚集索引叶子结点存放的是一整行的信息,而辅助索引叶子结点存放的是单个索引列信息.

何时使用聚集索引或非聚集索引

MySQL 之【MySQL 存储引擎】【索引】【慢查询优化】_第7张图片

 

2.4 正确使用索引

数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。

#1. 范围查询(>、>=、<、<=、!= 、between...and)
    #1. = 等号
    select count(*) from userinfo where id = 1000 -- 执行索引,索引效率高
    
    #2. > >= < <= between...and 区间查询
    select count(*) from userinfo where id <100; -- 执行索引,区间范围越小,索引效率越高
    select count(*) from userinfo where id >100; -- 执行索引,区间范围越大,索引效率越低
    select count(*) from userinfo where id between 10 and 500000; -- 执行索引,区间范围越大,索引效率越低
    
   #3. != 不等于
   select count(*) from userinfo where id != 1000;  -- 索引范围大,索引效率低
   
#2.like '%xx%'
    #为 name 字段添加索引
    create index idx_name on userinfo(name)
    select count(*) from userinfo where name like '%xxxx%'; -- 全模糊查询,索引效率低
    select count(*) from userinfo where name like '%xxxx';   -- 以什么结尾模糊查询,索引效率低
  
    #例外: 当like使用以什么开头会索引使用率高
    select * from userinfo where name like 'xxxx%'; 

#3. or 
    select count(*) from userinfo where id = 12334 or email ='xxxx'; -- email不是索引字段,索引此查询全表扫描
    
    #例外:当or条件中有未建立索引的列才失效,以下会走索引
    select count(*) from userinfo where id = 12334 or name = 'alex3'; -- id 和 name 都为索引字段时, or条件也会执行索引

#4.使用函数
    select count(*) from userinfo where reverse(name) = '5xela'; -- name索引字段,使用函数时,索引失效
    
    #例外:索引字段对应的值可以使用函数,我们可以改为一下形式
    select count(*) from userinfo where name = reverse('5xela');

#5.类型不一致
    #如果列是字符串类型,传入条件是必须用引号引起来,不然慢
    select count(*) from userinfo where name = 454;
    #类型一致
    select count(*) from userinfo where name = '454';

#6.order by
    #排序条件为索引,则select字段必须也是索引字段,否则无法命中  
    select email from userinfo ORDER BY name DESC; -- 无法命中索引

    select name from userinfo ORDER BY name DESC;  -- 命中索引
        
    #特别的:如果对主键排序,则还是速度很快:
    select id from userinfo order by id desc;

 

2.5 组合索引

组合索引: 是指对表上的多个列组合起来做一个索引.

 组合索引好处:简单的说有两个主要原因:

  • "一个顶三个"。建了一个(a,b,c)的组合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!
  • 索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是组合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页,哪个更高效,一眼便知 

最左匹配原则: 从左往右依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用;

select * from mytable where a=3 and b=5 and c=4;
   #abc三个索引都在where条件里面用到了,而且都发挥了作用

select * from mytable where  c=4 and b=6 and a=3;
  #这条语句列出来只想说明 mysql没有那么笨,where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样

select * from mytable where a=3 and c=7;
  #a用到索引,b没有用,所以c是没有用到索引效果的

select * from mytable where a=3 and b>7 and c=3;
  #a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引

select * from mytable where b=3 and c=4;
  #因为a索引没有使用,所以这里 bc都没有用上索引效果

select * from mytable where a>4 and b=7 and c=9;
  #a用到了  b没有使用,c没有使用

select * from mytable where a=3 order by b;
  #a用到了索引,b在结果排序中也用到了索引的效果

select * from mytable where a=3 order by c;
  #a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了

select * from mytable where b=3 order by a;
  #b没有用到索引,排序中a也没有发挥索引效果

 

 

2.6 注意事项

1. 避免使用select *

2. 其他数据库中使用count(1)或count(列) 代替 count(*),而mysql数据库中count(*)经过优化后,效率与前两种基本一样.

3. 创建表时尽量时 char 代替 varchar

4. 表的字段顺序固定长度的字段优先

5. 组合索引代替多个单列索引(经常使用多个条件查询时)

6. 使用连接(JOIN)来代替子查询(Sub-Queries)

7. 不要有超过4个以上的表连接(JOIN

8. 优先执行那些能够大量减少结果的连接。

9. 连表时注意条件类型需一致

10.索引散列值不适合建索引,例:性别不适合

 

三、慢查询优化

3.1 查询计划

explain + 查询SQL - 用于显示SQL执行信息参数,根据参考信息可以进行SQL优化

栗子:

explain  select count(*) from userinfo where  id = 1;
type性能: null > system/const > eq_ref > ref > ref_or_null > index_merge >  range > index >  all

 

3.2 慢日志查询

 慢查询日志 

   将mysql服务器中影响数据库性能的相关SQL语句记录到日志文件,通过对这些特殊的SQL语句分析,改进以达到提高数据库性能的目的。

慢查询日志参数:

long_query_time     :  设定慢查询的阀值,超出设定值的SQL即被记录到慢查询日志,缺省值为10s
slow_query_log      :  指定是否开启慢查询日志
log_slow_queries    :  指定是否开启慢查询日志(该参数已经被slow_query_log取代,做兼容性保留)
slow_query_log_file :  指定慢日志文件存放位置,可以为空,系统会给一个缺省的文件host_name-slow.log
log_queries_not_using_indexes: 如果值设置为ON,则会记录所有没有利用索引的查询.

#.查询慢日志配置信息 :
show variables like '%query%';
#.修改配置信息
set global slow_query_log  = on;

# 显示参数  
show variables like '%log_queries_not_using_indexes';
# 开启状态
set global log_queries_not_using_indexes  = on;

#查看慢日志记录的方式
show variables like '%log_output%';
 
#设置慢日志在文件和表中同时记录
set global log_output='FILE,TABLE';
#查看表中的日志
select * from mysql.slow_log;

 

3.3 大数据量分页优化 

优化方案:

一. 简单粗暴,就是不允许查看这么靠后的数据,比如百度就是这样的

最多翻到72页就不让你翻了,这种方式就是从业务上解决;

 

二.在查询下一页时把上一页的行id作为参数传递给客户端程序,然后sql就改成了

select * from userinfo where id>3000000 limit 10;

这条语句执行也是在毫秒级完成的,id>300w其实就是让mysql直接跳到这里了,不用依次在扫描全面所有的行

如果你的table的主键id是自增的,并且中间没有删除和断点,那么还有一种方式,比如100页的10条数据

select * from userinfo where id>100*10 limit 10;

 

三.最后第三种方法:延迟关联

我们在来分析一下这条语句为什么慢,慢在哪里。

select * from userinfo limit 3000000,10;

玄机就处在这个 * 里面,这个表除了id主键肯定还有其他字段  比如 name  age  之类的,因为select  *  所以mysql在沿着id主键走的时候要回行拿数据,走一下拿一下数据;

如果把语句改成

select id from userinfo limit 3000000,10;

你会发现时间缩短了一半;然后我们在拿id分别去取10条数据就行了;

语句就改成这样了:

select table.* from userinfo inner join ( select id from userinfo limit 3000000,10 ) as tmp on tmp.id=userinfo.id;

这三种方法最先考虑第一种 其次第二种,第三种是别无选择

 

 

 

 

你可能感兴趣的:(数据库MySQL)