MySQL之索引以及正确使用索引

一、MySQL中常见索引类型

  • 普通索引:仅加速查询
  • 主键索引:加速查询、列值唯一、表中只有一个(不可有null)
  • 唯一索引:加速查询、列值唯一(可以有null)
  • 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并

索引合并:使用多个单列索引组合搜索。

覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行;换句话说,查询列要被所建的索引覆盖。

普通索引

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

-- 单独为表指定普通索引
create index idx_name on tb(name);

-- 删除索引
drop index idx_name on tb;

-- 查看索引
show index from tb;
View Code
Table         # 表的名称

Non_unique    # 如果索引为唯一索引,则为0,如果可以则为1

Key_name     # 索引的名称

Seq_in_index  # 索引中的列序列号,从1开始

Column_name   # 列名称

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

Cardinality   # 索引中唯一值的数目的估计值

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

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

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

Index_type   # 用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)

Comment     # 多种评注
查看索引 --> 列介绍

主键索引

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

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

-- 创建表同时添加id字段为主键索引
-- 方式一
create table tb(
   id int not null auto_increment primary key,
   name char(4) not null
);

-- 方式二
create table tb(
   id int not null auto_increment,
   name char(4) not null,
   primary key(id)
);

-- 给某个已经存在的表增加主键
alter table tb add primary key(id);

-- 删除主键
-- 方式一
alter table tb drop primary key;

-- 方式二
-- 如果当前主键为自增主键,则不能直接删除,需要先修改自增属性,再删除
alter table tb modify id int,drop primary key;
View Code

唯一索引

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

create table tb(
  id int not null auto_increment primary key,
  name char(4) not null,
  age int not null,
  unique index idx_age (age)
);

-- 给某个已经存在的表创建唯一索引
create unique index idx_age on tb(age);
View Code

组合索引

组合索引是将n个列组合成一个索引
应用场景:频繁的同时使用n列来进行查询,如:select * from tb where name="pd" and id=888;

create table tb(
    id int not null,
    name char(4) not null,
    age int not null,
    index idx_name_age (name,age)
);

-- 给某个已经存在的表创建组合索引
create index idx_name_age on tb(name,age);
View Code

 二、聚集索引和非聚集索引(辅助索引)

 数据库中的 B+tree 索引可以分为:聚集索引和非聚集索引

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

①当你定义一个主键时,innodb 存储引擎则把它当做聚集索引;

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

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

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

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

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

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

聚集索引与辅助索引区别:

  • 相同的是:不管是聚集索引还是辅助索引,其内部都是 B+tree 形式,即高度是平衡的,叶子结点存放着所有的数据。
  • 不同的是:聚集索引叶子结点存放的是一整行的信息,而辅助索引叶子结点存放的是单个索引字段信息。

何时使用聚集索引或非聚集索引(重要!!!):

三、测试索引

1、创建表

create table userinfo(
    id int not null,
    name varchar(16) default null,
    age int,
    gender char(1) not null,
    email varchar(32) default null
)engine=myisam default charset=utf8;
View Code

注意:MYISAM存储引擎不产生引擎事务,数据插入速度极快,为方便快速插入测试数据,等我们插完数据,再把存储类型修改为InnoDB。

2、创建存储过程

create procedure insert_userinfo(in num int)
begin
    declare i int default 0;
    declare n int default 1;
    -- 循环进行数据插入
    while n<=num do
        set i=rand()*50;
        insert into userinfo(id,name,age,gender,email) values(n,concat("pink",i),rand()*50,if(i%2=0,"女","男"),concat("pink",n,"@qq.com"));
        set n=n+1;
    end while;
end;
View Code

3、调用存储过程,插入500万条数据

call insert_userinfo(5000000);

4、修改引擎为INNODB

alter table userinfo engine=innodb;

5、测试索引

①在没有索引的前提下测试查询速度

select * from userinfo where id=4567890;

注意:无索引情况,mysql根本就不知道id等于4567890的记录在哪里,只能把数据表从头到尾扫描一遍,此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢。

②在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢

create index idx_id on userinfo(id);

③在索引建立完毕后,以该字段为查询条件时,查询速度提升明显

select * from userinfo where id=4567890;

注意:

  1. mysql先去索引表里根据 b+树 的搜索原理很快搜索到id为4567890的数据,IO大大降低,因而速度明显提升
  2. 我们可以去mysql的data目录下找到该表,可以看到添加索引后该表占用的硬盘空间多了
  3. 如果使用没有添加索引的字段进行条件查询,速度依旧会很慢(如下图)

四、正确使用索引

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

-- 范围查询(>、>=、<、<=、!= 、between...and)
    -- = 等号
    select count(*) from userinfo where id=1000-- 执行索引,索引效率高
    
    -- >、>=、<、<=、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; -- 执行索引,区间范围越大,索引效率越低
    
    -- != 不等于
    select count(*) from userinfo where id!=1000; -- 索引范围大,索引效率低
   
 
-- 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%";


-- or select count(*) from userinfo where id=1000 or email="xx"; -- email不是索引字段,索引此查询全表扫描 -- 例外:当or条件中有未建立索引的列才失效,以下会走索引 select count(*) from userinfo where id=1000 or name="pink3"; -- id和name都为索引字段时,or条件也会执行索引
-- 使用函数 select count(*) from userinfo where reverse(name)="1knip"; -- name索引字段,使用函数时,索引失效 -- 例外:索引字段对应的值可以使用函数,我们可以改为以下形式 select count(*) from userinfo where name=reverse("1knip");
-- 类型不一致 -- 如果列是字符串类型,传入条件是必须用引号引起来 select count(*) from userinfo where name=123; -- -- 类型一致 select count(*) from userinfo where name="123"; --
-- order by -- 排序条件为索引,则select字段必须也是索引字段,否则无法命中 select email from userinfo order by name desc; -- 无法命中索引 select name from userinfo order by name desc; -- 命中索引

五、组合索引

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

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

  • 一个顶三个;建了一个(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 tb where a=1 and b=2 and c=3;
-- abc三个索引都在where条件里面用到了,而且都发挥了作用

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

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

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

select * from tb where a>1 and b=2 and c=3;
-- a用到了,b没有使用,c没有使用

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

select * from tb where a=1 order by c;
-- a用到了索引,但是c没有发挥排序效果,因为中间断点了

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

、注意事项(重要)

1、避免使用select *
2、其他数据库中使用count(1)或count(列)代替count(*),而mysql数据库中count(*)经过优化后,效率与前两种基本一样
3、创建表时尽量时 char 代替 varchar
4、表的字段顺序固定长度的字段优先
5、组合索引代替多个单列索引(经常使用多个条件查询时)
6、使用连接(JOIN)来代替子查询(Sub-Queries)
7、不要有超过4个以上的表连接(JOIN)
8、优先执行那些能够大量减少结果的连接
9、连表时注意条件类型需一致
10、索引散列值不适合建索引,例:性别不适合

七、慢查询日志

慢查询日志:将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,则会记录所有没有利用索引的查询

查看MySQL慢日志信息:

-- 查询慢日志配置信息
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";

测试慢查询日志:

-- 查询时间超过10秒就会记录到慢查询日志中
select sleep(3) from userinfo;
-- 查看表中的日志
select * from mysql.slow_log;

八、执行计划

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

explain  select count(*) from userinfo where  id=1;

执行计划:让mysql预估执行操作(一般正确)。

type:查询计划的连接类型, 有多个参数,先从最佳类型到最差类型介绍
性能:null > system/const > eq_ref > ref > ref_or_null > index_merge >  range > index >  all 

慢:
    explain select * from userinfo where email="pink";
    type: ALL(全表扫描)
    特别的: select * from userinfo limit 1;
    
快:
    explain select * from userinfo where name="alex";
    type: ref(走索引)

EXPLAIN 参数详解

九、大数据量分页优化

执行此段代码:

select * from userinfo limit 3000000,10;

优化方案:

1、简单粗暴,不允许查看这么靠后的数据。比如百度就是这样的,最多翻到76页就不让你翻了,这种方式就是从业务上解决。

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

select * from userinfo where id>3000000 limit 10;

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

3、延迟关联

分析一下这条语句为什么慢,慢在哪里:

select * from userinfo limit 3000000,10;

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

select id from userinfo limit 3000000,10;

你会发现时间缩短了很多;然后我们在拿id分别去取10条数据就行了。语句就改成了这样:

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

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

原文地址:https://www.cnblogs.com/believepd/p/9774116.html