首页 > 技术文章 > MYSQL 索引汇总

start-from-zero 2020-03-21 10:52 原文

1.MySQL索引类型

先分以下类,MYQL有两大类索引:聚集索引和非聚集索引(只考虑mysql innodb)

聚集索引:在有主键的情况下,主键为聚集索引,其他都是非聚集索引

                  在没有主键的情况下,使用唯一索引作为聚集索引,其他都是非聚集索引

                  在没有主键没有唯一索引的时候mysql创建隐藏列作为唯一索引,其他都是非聚集索引

##聚集索引叶子节点存放表的整行数据

 

非聚集索引,除了聚聚索引外的全部索引,下面有介绍

## 只保存索引列和主键的值(例如我我在字段 name上加一个二级普通索引,就只存在name的值和主键的值)

 

聚集索引索引和非聚集索引的区别: 聚集索引的逻辑顺序对应磁盘的物理顺序,也就是改变聚集索引列的值时会移动磁盘的物理位置会把该主键对应的记录的物理位置,也就是磁盘位置也更换,这样不但效率不高,可能面临页分裂的问题,当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作,页分裂会导致表占用更多的磁盘空间。聚集索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。

## 疑问:关于非聚集索引的逻辑顺序不对应磁盘物理位置,也不敢肯定,以后直到了回来填坑

## 推测:非聚集索引在达到索引覆盖时(就是不用回表,在二级索引能找到值的时候),感觉会比主键要快一点(假如表的列很多的时候,聚集索引的B+tree会比非聚集索引的B+tree要高,假如一页有16K,列越多,单行数据越大,一页能放下的行数越小,从而树会比二级索引要高一些)

例如:select name from test where name='lxl'  ---name是二级普通索引,就是覆盖索引,select 的列被使用的索引包含(如何看索引是否被使用--EXPLAIN,下面有说)

## 关于推测和疑问看看就好,不肯定。

 

主键索引

在INNODB下且有主键的情况下,数据文件和主键索引文件是同一个文件

##除叶子节点外是索引,叶子节点保存表的全部记录

创建主键索引操作:
CREATE TABLE t5 ( col1 INT NOT NULL, col2 CHAR NOT NULL, col3 INT NOT NULL, col4 INT NOT NULL, PRIMARY KEY(col1,col2) )
或者:
CREATE TABLE t5 ( col1 INT NOT NULL primary key, col2 CHAR NOT NULL, col3 INT NOT NULL, col4 INT NOT NULL )
 
删除主键索引:
alter table test drop primary key
##在有自增的时候,要取消自增再删除主键
alter table test modify column id int not null;
 
给已有列增加主键索引:
alter table test add primary key(id)

 


唯一索引

在没有主键的情况下,充当primary key 的角色

 

 

 

在有主键的情况下,跟单列索引的区别是多了个唯一约束,

 

叶子节点除了记录索引列的数据外还记录主键,在没有达到索引覆盖时会在辅助索引上找到该列的主键,再通过主键到数据文件(索引文件)中找到全部数据

 

创建唯一索引操作:
CREATE TABLE t5 (
    col1 INT NOT NULL,
    col2 CHAR NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL,
    UNIQUE KEY(col1,col2)
)
或者:
CREATE TABLE t5 (
    col1 INT NOT NULL UNIQUE KEY,
    col2 CHAR NOT NULL,
    col3 INT NOT NULL,
    col4 INT NOT NULL  
)
 
删除唯一索引:
show index from test;  ---先查看索引
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| t9    |          0 | col1     |            1 | col1        | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| t9    |          0 | col1_2   |            1 | col1        | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+-
alter table t9 drop index col1;
alter table t9 drop index col2;
 
给现有列增加唯一索引
alter table t9 modify column col1 int not null unique key;
或者:
alter table t9 add unique(col1)

 

组合索引

创建组合索引:
CREATE TABLE test10 (
    id         INT NOT NULL,
    last_name  CHAR(30) NOT NULL,
    first_name CHAR(30) NOT NULL,
    PRIMARY KEY (id),
    INDEX name (last_name,first_name)
);
删除组合索引:
show index from test10;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test10 |          0 | PRIMARY  |            1 | id          | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| test10 |          1 | name     |            1 | last_name   | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| test10 |          1 | name     |            2 | first_name  | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
alter table test10 drop index name;
 
给现有列增加组合索引
alter table test10 add index(last_name,first_name);

 

 

 

单列索引

 

 

 

叶子节点除了记录索引列的数据外还记录主键,在没有达到索引覆盖时会在辅助索引上找到该列的主键,再通过主键到数据文件(索引文件)中找到全部数据

创建单列索引:
CREATE TABLE test11 (
    id         INT NOT NULL,
    last_name  CHAR(30) NOT NULL,
    first_name CHAR(30) NOT NULL,
    PRIMARY KEY (id),
    INDEX name (last_name)
);
 
删除单列索引:
show index from test11;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test11 |          0 | PRIMARY  |            1 | id          | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| test11 |          1 | name     |            1 | last_name   | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
alter table test11 drop index name;
 
给现有列增加组合索引
alter table test10 add index(last_name);

 

 

 

外键约束

外键加强了数据的一致性,但是 

每次子表更新操作时都会对父表select 一次, 

每次父表删除的时候,都对子表删除对应信息 

[CONSTRAINT [symbol]] FOREIGN KEY
    [index_name] (col_name, ...)
    REFERENCES tbl_name (col_name,...)
    [ON DELETE reference_option]
    [ON UPDATE reference_option]
 
reference_option:
    RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT
 
RESTRICT:拒绝父表的删除或更新操作。指定 RESTRICT(或NO ACTION)与省略ON DELETEor ON UPDATE子句相同
CASCADE:从父表中删除或更新该行,并自动删除或更新子表中的匹配行
NO ACTION: 等效于RESTRICT
SET NULL:从父表中删除或更新该行,并将子表中的外键列设置为NULL
SET DEFAULT:MySQL解析器可以识别此操作,但是两者都拒绝
 
创建外键约束
------ 父表 ------
CREATE TABLE parent (
    id INT NOT NULL,
    PRIMARY KEY (id)
) ENGINE=INNODB;
 
------ 子表 ------
CREATE TABLE child (
    id INT,
    parent_id INT,
    INDEX par_ind (parent_id),
    FOREIGN KEY (parent_id)
        REFERENCES parent(id)
        ON DELETE CASCADE
) ENGINE=INNODB;
 
给现有表增加外键约束
ALTER TABLE test.PERSON ADD FOREIGN KEY(department_id) references test.department(id)
 
删除现有外键约束
show index from test.person
+--------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table  | Non_unique | Key_name      | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| person |          0 | PRIMARY       |            1 | id            | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
| person |          1 | department_id |            1 | department_id | A         |           0 |     NULL | NULL   |      | BTREE      |         |               |
+--------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
 
 
ALTER TABLE tbl_name DROP FOREIGN KEY department_id;

 

2.EXPLAIN

查看方法  EXPLAIN + SELECT 语句

## 执行  explain select * from test.test;

2.select_type  ---查询的类型 

SIMPLE  --简单select,不使用UNION或者子查询, 使用连结也是simple(例如inner join)

 

PRIMARY  --子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY

 

## 子查询中需要达到一定数据量

UNION    — UNION中第二个或者后面的SELECT 语句

 

DEPENDENT UNION   --UNION中的第二个或后面的SELECT语句,有外部的查询的情况下

 

UNION RESULT   --UNION的结果,union语句中第二个select开始后面所有select

 

SUBQUERY  --在select 或 where列表中包含了子查询的第一个子查询

 

DEPENDENT SUBQUERY   --子查询中的第一个SELECT,取决于外面的查询

 

DERIVED  --派生表的SELECT(FROM子句的子查询)

 

UNCACHEABLE SUBQUERY   ---一个子查询,对于外面的主表,子查询结果不能被缓存,每次都要重新检索

 

UNCACHEABLE UNION   ---UNION操作中,结果不能被缓存

 

3.table

显示查询的表名

如果查询使用了别名,那么显示别名

如果不涉及数据表的操作,显示null

如果显示为尖括号括起来的,表示这是一个临时表  <>

 

4.type

效率排序:

system  >  const  >   eq_ref  >   ref  >  range  > index   > all       (index_merge之类的不加入效率排序,跟具体情况有关)

 

system   ---const中的一个特例,表中只有一条记录,无论是否使用唯一索引或者主键

 

const   ---使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const

 

eq_ref   --在关联查询中使用唯一索引或者主键索引作为连接条件

 

ref   --使用普通索引的定值查找,连接查询时使用普通索引作关联条件

 

range   ---使用索引的范围查找

 

index    ---遍历B+树

 

##Innodb存储引擎

##这里第一条达到索引覆盖,不需要回主键表读取(INNODB主键和数据是一个文件同一颗B+tree,保存所有数据),普通索引文件保存主键id以及索引那列的值,

##在mysql没达到索引覆盖的时候,mysql检索的时候认为扫表(遍历索引文件,就是遍历树的叶子节点)快一点就不去走索引了

all     ----遍历B+树的叶子节点

 

5.possible_keys

指出MySQL在检索时能使用哪个索引,但不一定被查询使用

 

6.key

key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中

7.key_len

id int key_len = 4+1 int为4bytes,允许为NULL,加1byte
id bigint not null key_len=8 bigint为8bytes
user char(30) utf8 key_len=30*3+1 utf8每个字符为3bytes,允许为NULL,加1byte
user varchar(30) not null utf8 key_len=30*3+2 utf8每个字符为3bytes,变长数据类型,加2bytes
user varchar(30) utf8 key_len=30*3+2+1 utf8每个字符为3bytes,允许为NULL,加1byte,变长数据类型,加2bytes
列类型
KEY_LEN
备注

mysql检索数据时所使用索引的长度

使用的索引的长度。在不损失精确性的情况下,长度越短越好

用于计算组合索引中用到了几个索引

8.ref

如果是使用的常数等值查询,这里会显示const

如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段

如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func。

 

==================  rows  =================

这里是执行计划中估算的扫描行数,不是精确值

================== extra  ==================

distinct    --在找到第一匹配的元组后即停止找同样值的动作

no table used   ----不带from的select 语句,或者使用了from dual

using filesort     ---MySQL必须额外进行一遍,同时使用不到索引

using index   --索引覆盖,不需要回表查询

using temporapy    ---查询中使用了临时表作为中间结果

using where   ---使用了where条件

using interset   ---index-merge时,表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集

using union:   ---表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集

 

3 一条SQL在使用索引的时候是怎么找的

================== 从B+tree角度分析  ========================

在Innodb存储引擎中

1.每新增一个索引,就会增加一个索引文件,保存在data目录的对应数据库中

2.索引文件实质是一个B+tree结构

 

 

##假如给一个表的一个字段建立一个辅助索引,索引保存的该字段数据为 1,2,3,5,6,8,9,11,13,15

3.假如要找的数据为3(where 该字段=3)

第一次磁盘IO为(8,15)的的节点

第二次磁盘IO为(2,5,8)的节点

第三次磁盘IO为(3,5)的节点

所以要找到3这个数据大概3次磁盘IO

当然,也不是说查找这个3 要等待3次磁盘IO,

根据局部性原理于磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存

###在没有索引的时候,如果一般where 字段=30

 

 

在where的字段没有索引的情况,会根据这个链表的顺序从头遍历下去,直到找到30。

ps:在B+tree中叶子节点形成一个链表

ps:在Innodb下,数据文件也是一颗B+树

ps:   别以为二层索引就放这几条数据,假如一个页16K,二级索引一个索引列(索引列+主键值)为10B,然后一个节点就能放1600个索引列,二层索引结构,叶子节点就有1600^3,然后每个叶子节又有1600个索引列,就是能放1600^4个索引列

4 如何设计索引?

1.根据索引的选择性来建立索引       ---是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:Index Selectivity = Cardinality / #T

选择性越高的索引价值越大 ,SELECT count(DISTINCT(name))/count(*) AS Selectivity FROM test.test;

###有一种与索引选择性有关的索引优化策略叫做前缀索引----用列的前缀值代替整个列作为索引key,当前缀长度合适时,可以做到前缀索引的选择性接近全列索引,同时因为索引长度减少了从而减少索引文件的开销和维护。

###虽然前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于order by 和group by。也不能用于索引覆盖

 2.(待完善)

 

 

推荐阅读