MySQL 之INDEX 索引
1.4 INDEX 索引
1.4.1 索引介绍
索引:是排序的快速查找的特殊数据结构,定义作为查找条件的字段上,又称为键 key,索引通过存储引擎实现。
优点
-
大大加快数据的检索速度;
-
创建唯一性索引,保证数据库表中每一行数据的唯一性;
-
加速表和表之间的连接;
-
在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
缺点
-
索引需要占物理空间。
-
当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度。
索引类型
-
B+ TREE、HASH、R TREE、FULL TEXT
-
聚簇(集)索引、非聚簇索引:数据和索引是否存储在一起
-
主键索引、二级(辅助)索引
-
稠密索引、稀疏索引:是否索引了每一个数据项
-
简单索引、组合索引: 是否是多个字段的索引
-
左前缀索引:取前面的字符做索引
-
覆盖索引:从索引中即可取出要查询的数据,性能高
1.4.2 索引结构
参考链接:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
树的简介
树跟数组、链表、堆栈一样,是一种数据结构。它由有限个节点,组成具有层次关系的集合。因为它看起来像一棵树,所以得其名。
树是包含n(n为整数,大于0)个结点, n-1条边的有穷集,它有以下特点
-
每个结点或者无子结点或者只有有限个子结点
-
有一个特殊的结点,它没有父结点,称为根结点
-
每一个非根节点有且只有一个父节点
-
树里面没有环路
概念相关
-
结点的度:一个结点含有的子结点个数称为该结点的度
-
树的度:一棵树中,最大结点的度称为树的度
-
父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点
-
深度:对于任意结点N,N的深度为从根到n的唯一路径长,根结点的深度为0
-
高度:对于任意结点N,N的高度为从n到一片树叶的最长路径长,所有树叶的高度为0
树的分类
按照有序性,可以分为有序树和无序树:
-
无序树:树中任意节点的子结点之间没有顺序关系
-
有序树:树中任意节点的子结点之间有顺序关系
按照节点包含子树个数,可以分为B树和二叉树,二叉树可以分为以下几种:
-
二叉树:每个节点最多含有两个子树的树称为二叉树
-
二叉查找树:首先它是一颗二叉树,若左子树不空,则左子树上所有结点的值均小于它的根结点的值;若右子树不空,则右子树上所有结点的值均大于它的根结点的值,左、右子树也分别为二叉排序树
-
满二叉树:叶节点除外的所有节点均含有两个子树的树被称为满二叉树
-
完全二叉树:如果一颗二叉树除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布
-
霍夫曼树:带权路径最短的二叉树
-
红黑树:红黑树是一种特殊的二叉查找树,每个节点都是黑色或者红色,根节点、叶子节点是黑色。如果一个节点是红色的,则它的子节点必须是黑色的
-
平衡二叉树(AVL):一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
二叉树
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。
参考链接:https://www.cs.usfca.edu/~galles/visualization/BST.html
红黑树
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
红黑树特点
-
根节点是黑色的,叶节点是不存储数据的黑色空节点
-
任何相邻的两个节点不能同时为红色,红色节点被黑色节点隔开,红色节点的子节点是黑色的
-
任意节点到其可到达的叶节点间包含相同数量的黑色节点,保证任何路径相差不会超出2倍,从而实现基本平衡
参考链接:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
B-树
B-树,读作B树,中间的横线是连字符,不是减号,B树将一个节点的大小设置为每个数据页(Page,也可以称为块,block)的大小,一般是16KB,并且,B树中是将数据和索引放在一起的,以减少IO次数,加快查询速度,一个节点能放多少数据,通常取决于一条数据占用的空间大小。
mysql> show variables like '%page_size%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 |
| large_page_size | 0 |
+------------------+-------+
2 rows in set (0.00 sec)
B+树索引
B+树是B-树的变体,也是一棵多路搜索树,MySQL普遍使用B+树来实现索引。
https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
B+树和B-树的主要区别:
-
B-树内部节点是保存数据的,而B+树内部节点是不保存数据的,只作索引作用,它的叶子节点才保存数据。
-
B+树相邻的叶子节点之间是通过链表指针连起来的,B-树却不是。
-
查找过程中,B-树在找到具体的数值以后就结束,而B+树则需要通过索引找到叶子结点中的数据才结束
-
B-树中任何一个关键字出现且只出现在一个结点中,而B+树可以出现多次。
可以使用B+Tree索引的查询类型(假设前提:姓,名,年龄三个字段建立了一个复合索引)
-
全值匹配:精确所有索引列,如:姓zhang,名ming,年龄20
-
匹配最左前缀:即只使用索引的第一列,如:姓zhang
-
匹配列前缀:只匹配一列值开头部分,如:姓以w开头的记录
-
匹配范围值:如:姓wang和姓zhang之间
-
精确匹配某一列并范围匹配另一列:如:姓wang,名以x开头的记录
-
只访问索引的查询
B+Tree索引的限制
-
如不从最左列开始,则无法使用索引,如:查找名为xiaoming,或姓为g结尾
-
不能跳过索引中的列:如:查找姓为wang,年龄30的,只能使用索引第一列
特别提示
-
索引列的顺序和查询语句的写法应相匹配,才能更好的利用索引
-
为优化性能,可能需要针对相同的列但顺序不同创建不同的索引来满足不同类型的查询需求
Hash索引
-
Hash索引:基于哈希表实现,只有精确匹配索引中的所有列的查询才有效,索引自身只存储索引列对应的哈希值和数据指针,索引结构紧凑,查询性能好。
-
Memory 存储引擎支持显式 hash 索引,InnoDB 和 MyISAM 存储引擎不支持。
-
适用场景:只支持等值比较查询,包括=,<=>,IN()。
不适合使用hash索引的场景
-
不适用于顺序查询:索引存储顺序的不是值的顺序
-
不支持模糊匹配
-
不支持范围查询
-
不支持部分索引列匹配查找:如A,B列索引,只查询A列索引无效
地理空间数据索引 R-Tree( Geospatial indexing )
MyISAM 支持地理空间索引,可使用任意维度组合查询,使用特有的函数访问,常用于做地理数据存储,使用不多。
InnoDB从MySQL5.7之后也开始支持
全文索引(FULLTEXT)
在文本中查找关键词,而不是直接比较索引中的值,类似搜索引擎
InnoDB 从 MySQL 5.6 之后也开始支持
聚簇和非聚簇索引,主键和二级索引
在 MySQL 的 InnoDB 引擎中,每个索引都会对应一棵 B+ 树,而聚簇索引和非聚簇索引最大的区别在于叶子节点存储的数据不同,聚簇索引叶子节点存储的是行数据,因此通过聚簇索引可以直接找到真正的行数据;而非聚簇索引叶子节点存储的是主键信息,所以使用非聚簇索引还需要回表查询,因此我们可以得出聚簇索引和非聚簇索引的区别主要有以下几个:
-
聚簇索引叶子节点存储的是行数据;而非聚簇索引叶子节点存储的是聚簇索引(通常是主键 ID)。
-
聚簇索引查询效率更高,而非聚簇索引需要进行回表查询,因此性能不如聚簇索引。
-
聚簇索引一般为主键索引,而主键一个表中只能有一个,因此聚簇索引一个表中也只能有一个,而非聚簇索引则没有数量上的限制。
冗余和重复索引
-
冗余索引:(A),(A,B),注意如果同时存在,仍可能会使用(A)索引
-
重复索引:已经有索引,再次建立索引
1.4.3 索引优化
参考资料:阿里的《Java开发手册》
https://developer.aliyun.com/topic/java2020
-
独立地使用列:尽量避免其参与运算,独立的列指索引列不能是表达式的一部分,也不能是函数的
-
参数,在where条件中,始终将索引列单独放在比较符号的一侧,尽量不要在列上进行运算(函数操作和表达式操作)
-
左前缀索引:构建指定索引字段的左侧的字符数,要通过索引选择性(不重复的索引值和数据表的记录总数的比值)来评估,尽量使用短索引,如果可以,应该制定一个前缀长度
-
多列索引:AND操作时更适合使用多列索引,而非为每个列创建单独的索引
-
选择合适的索引列顺序:无排序和分组时,将选择性最高放左侧
-
只要列中含有NULL值,就最好不要在此列设置索引,复合索引如果有NULL值,此列在使用时也不会使用索引
-
对于经常在where子句使用的列,最好设置索引
-
对于有多个列where或者order by子句,应该建立复合索引
-
对于like语句,以 % 或者 _ 开头的不会使用索引,以 % 结尾会使用索引
-
尽量不要使用not in和<>操作,虽然可能使用索引,但性能不高
-
不要使用RLIKE正则表达式会导致索引失效
-
查询时,能不要就不用,尽量写全字段名,比如:select id,name,age from students;
-
大部分情况连接效率远大于子查询
-
在有大量记录的表分页时使用limit
-
对于经常使用的查询,可以开启查询缓存
-
多使用explain和profile分析查询语句
-
查看慢查询日志,找出执行时间长的sql语句优化
1.4.4 管理索引
查看帮助
mysql> help index
查看索引
SHOW INDEX FROM [db_name.]tbl_name;
mysql> desc student;
+--------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+----------------+
| id | int unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(20) | NO | | NULL | |
| age | tinyint unsigned | YES | | NULL | |
| gender | enum('M','F') | YES | | M | |
+--------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
#默认会在主键上创建索引
mysql> show index from student\G
*************************** 1. row ***************************
Table: student
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 6
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE #B树索引
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.01 sec)
查看语句是否利用索引
mysql> explain select * from student where id=12\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: const #只读取一次
possible_keys: PRIMARY
key: PRIMARY #使用了主键索引
key_len: 4
ref: const
rows: 1 #扫描了1条数据
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from student where name="wangwu"\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ALL #全表扫描
possible_keys: NULL #没有使用索引字段
key: NULL #没有使用索引字段
key_len: NULL
ref: NULL
rows: 6 #扫描了6条数据,整个表就是6条数据
filtered: 16.67
Extra: Using where #在存储引擎检索行后再进行过滤
1 row in set, 1 warning (0.00 sec)
创建索引
CREATE [UNIQUE] INDEX index_name ON tbl_name (index_col_name[(length)],...);
ALTER TABLE tbl_name ADD INDEX index_name(index_col_name[(length)]);
mysql> create index idx_name on student(name);
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
#create index idx_name on student(name(10)); 表示取 name 字段中的前 10 个字符做索引
再次查看
mysql> show index from student\G
*************************** 1. row ***************************
Table: student
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 6
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: student
Non_unique: 1
Key_name: idx_name
Seq_in_index: 1
Column_name: name
Collation: A
Cardinality: 6
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
2 rows in set (0.00 sec)
再次测试
mysql> explain select * from student where name="wangwu"\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ref
possible_keys: idx_name #使用了索引
key: idx_name #使用了索引
key_len: 62
ref: const
rows: 1 #只扫描1条数据
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
不是所有查询都能用到索引,B+树索引是左前缀特性,即左匹配可以使用索引
#like 查询左匹配可以使用索引
mysql> explain select * from student where name like 'g%'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: range
possible_keys: idx_name #索引
key: idx_name #索引
key_len: 62
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
#like 查询右匹配不使用索引
mysql> explain select * from student where name like '%g'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ALL #全表扫描
possible_keys: NULL #不走索引
key: NULL #不走索引
key_len: NULL
ref: NULL
rows: 6
filtered: 16.67
Extra: Using where
1 row in set, 1 warning (0.00 sec)
#like 查询包含匹配不使用索引
mysql> explain select * from student where name like '%g%'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 6
filtered: 16.67
Extra: Using where
1 row in set, 1 warning (0.00 sec)
mariadb 中关于索引利用的优化
MariaDB [testdb]> select count(*) from student;
+----------+
| count(*) |
+----------+
| 28 |
+----------+
1 row in set (0.000 sec)
MariaDB [testdb]> select count(*) from student where name like 'm%';
+----------+
| count(*) |
+----------+
| 22 |
+----------+
1 row in set (0.000 sec)
MariaDB [testdb]> select count(*) from student where name like 'z%';
+----------+
| count(*) |
+----------+
| 3 |
+----------+
1 row in set (0.000 sec)
MariaDB [testdb]> show index from student\G
*************************** 1. row ***************************
Table: student
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 28
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: student
Non_unique: 1
Key_name: idx_name
Seq_in_index: 1
Column_name: name
Collation: A
Cardinality: 28
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
2 rows in set (0.001 sec)
MariaDB [testdb]> explain select * from student where name like 'm%'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
type: ALL #全表扫描
possible_keys: idx_name #可能会用到的索引
key: NULL #没有使用索引
key_len: NULL
ref: NULL
rows: 28
Extra: Using where
1 row in set (0.000 sec)
MariaDB [testdb]> explain select * from student where name like 'z%'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
type: range #范围查询
possible_keys: idx_name #可能会用到的索引
key: idx_name #实际使用了索引
key_len: 62
ref: NULL
rows: 3
Extra: Using index condition
1 row in set (0.000 sec)
全表总共28条记录,name 字段中,以 m 开头的有22条,以 z 开头的有3条,所以在此情况下,查询以 m 开头内容,直接全表扫描反而会更快
这是 mariadb 中的优化,MySQL8.0 中也有此功能,但旧版本中没有此优化
删除索引
DROP INDEX index_name ON tbl_name;
ALTER TABLE tbl_name DROP INDEX index_name(index_col_name);
#删除索引
mysql> drop index idx_name on student;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
再次测试
mysql> explain select * from student where name="wangwu"\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ALL #全表扫描
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 6
filtered: 16.67
Extra: Using where
1 row in set, 1 warning (0.01 sec)
查看索引的使用
SET GLOBAL userstat=1;
SHOW INDEX_STATISTICS;
#仅在mariadb中使用
MariaDB [testdb]> select @@userstat;
+------------+
| @@userstat |
+------------+
| 0 |
+------------+
1 row in set (0.000 sec)
#开启
MariaDB [testdb]> SET GLOBAL userstat=1;
Query OK, 0 rows affected (0.000 sec)
#查看
MariaDB [testdb]> SHOW INDEX_STATISTICS;
Empty set (0.001 sec)
#查询
MariaDB [testdb]> select * from student where id=13;
+----+----------+------+--------+
| id | name | age | gender |
+----+----------+------+--------+
| 13 | zhangfei | 20 | F |
+----+----------+------+--------+
1 row in set (0.000 sec)
MariaDB [testdb]> select * from student where name like 'z%';
+----+-------------+------+--------+
| id | name | age | gender |
+----+-------------+------+--------+
| 13 | zhangfei | 20 | F |
| 11 | zhangsan | 12 | M |
| 16 | zhugeiliang | 39 | M |
+----+-------------+------+--------+
3 rows in set (0.001 sec)
#再次查看
MariaDB [testdb]> SHOW INDEX_STATISTICS;
+--------------+------------+------------+-----------+
| Table_schema | Table_name | Index_name | Rows_read |
+--------------+------------+------------+-----------+
| testdb | student | idx_name | 3 |
| testdb | student | PRIMARY | 1 |
+--------------+------------+------------+-----------+
2 rows in set (0.000 sec)
优化表空间
OPTIMIZE TABLE tb_name;
对 MySQL 进行大量或频繁的写操作(insert,delete,update),容易产生碎片,这些碎片会影响MySQL 性能。在此情况下,我们可以通过 optimize 命令来进行优化。此命令在使用时会锁表,需要保证在不对业务产生影响的情况下使用。
这里的碎片指的是,经过某些操作,导致数据库中的表对应的硬盘上的物理文件中的数据不是紧密排列的。
1.4.5 EXPLAIN 工具
可以通过EXPLAIN来分析索引的有效性,获取查询执行计划信息,用来查看查询优化器如何执行查询。
官方文档
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
格式
EXPLAIN SELECT clause
mysql> explain select * from student where id=13\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: const
possible_keys: PRIMARY #可能用到的键
key: PRIMARY
key_len: 4
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
EXPLAIN输出信息说明
-
id:执行编号,标识select所属的行。如果在语句中没子查询或关联查询,只有唯一的 select,每行都将显示1。否则,内层的 select 语句一般会顺序编号,对应于其在原始语句中的位置,如果explain的结果包括多个id值,则数字越大越先执行;而对于相同id的行,则表示从上往下依次执行。
-
select_type:查询类型,具体见下表。
-
table:具体查询的表名,如查 SQL 语句中定义了别名,则此处显示别名 。
-
partitions:当前查询匹配记录的分区。对于未分区的表,返回 NULL。
-
type:关联类型或访问类型,即 MySQL 决定的如何去查询表中的行的方式,具体见下表。
-
possible_keys:查询可能会用到的索引,此处列出的索引字段是在真正执行查询前的优化过程中创建的,因此有些不会被使用。
-
key:实际查询中用到的索引 。
-
key_len:实际查询中,使用索引数据中的字节数,可通过该列计算查询中使用的索引的长度,key_len 显示的值为索引字段的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的,不损失精确性的情况下,长度越短越好 。
-
ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息,列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值,如果 ref 是一个函数,则使用的值是函数的结果。要想查看是哪个函数,可在EXPLAIN语句之后紧跟一个SHOW WARNING语句。
-
rows:为了找到所需的行而需要读取的行数,估算值,不精确。
-
filtered:按表条件过滤的行百分比,表示符合查询条件的数据百分比,最大100。用 rows × filtered 可获得和下一张表连接的行数。
-
Extra:展示有关本次查询的附加信息,具体见下表。
select_type 查询类型具体说明
关键字 | 说明 |
SIMPLE | 简单查询(没有使用 UNION 或子查询) |
PRIMARY | 最外层的查询 |
UNION | 联合查询,在 UNION 中的第二个和随后的 SELECT 被标记为 UNION,如果UNION 被 FROM 子句中的子查询包含,那么它的第一个 SELECT 会被标记为DERIVED |
DEPENDENTUNION | UNION 中的第二个或后面的查询,依赖了外层查询 |
UNION RESULT | UNION 查询结果 |
SUBQUERY | 子查询中的第一个 SELECT 查询 |
DEPENDENT SUBQUERY | 子查询中的第一个 SELECT 查询,依赖了外层查询 |
DERIVED | 用来表示包含在 FROM 子句的子查询中的 SELECT , MySQL 会递归执行并将结果放到一个临时表中,MySQL 内部将此临时表称为 DERIVED table(派生表),因为该临时表是从子查询中派生出来的 |
DEPENDENT DERIVED | 派生表,并且有依赖于其它表 |
MALTERIALIZED | 物化子查询 |
UNCACHEABLE SUBQUERY | 子查询,但结果无法缓存,必须对于外部查询的每一行重新评估 |
UNCACHEABLEUNION | UNION 属于 UNCACHEABLE SUBQUERY 的第二个或后面的查询 |
type 关联类型具体说明,性能从好到坏排序
关键字 | 说明 |
NULL | MySQL 在优化过程中分解语句,执行时甚至不用访问表或索引,此种查询最高效 |
system | 该表只有一行(相当于系统表),system 是 const 类型的特例 |
const | 针对主键或唯一索引的等值查询扫描,,最多只返回一行数据, const 查询速度非常快, 它仅仅读取一次即可。 |
eq_ref | 当使用了索引的全部组成部分,并且索引是PRIMARY KEY或UNIQUE NOT NULL 才会使用该类型 |
ref | 当满足索引的最左前缀规则,或者索引不是主键也不是唯一索引时才会发生。如果使用的索引只会匹配到少量的行 |
fulltext | 全文索引 |
ref_or_null | 该类型类似于 ref,但是 MySQL 会额外搜索哪些行包含了 NULL。这种类型常见于解析子查询。 |
index_merge | 此类型表示使用了索引合并优化,表示一个查询里面用到了多个索引 |
unique_subquery | 该类型和 eq_ref 类似,但是使用了 IN 查询,且子查询是主键或者唯一索引 |
index_subquery | 和 unique_subquery 类似,只是子查询使用的是非唯一索引 |
range | 范围扫描,表示检索了指定范围的行,主要用于有限制的索引扫描。比较常见的范围扫描是带有BETWEEN子句或WHERE子句里有>、>=、<、<=、IS NULL、<=>、BETWEEN、LIKE、IN()等操作符 |
index | 全索引扫描,和ALL类似,只不过index是全盘扫描了索引的数据。当查询仅使用索引中的一部分列时,可使用此类型 |
ALL | 全表扫描,性能最差 |
Extra 额外信息说明
关键字 | 关键字 |
Using where | 不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示 MySQL 服务器将在存储引擎检索行后再进行过滤。 |
Using temporary | 表示 MySQL 需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by,order by。 |
Using filesort | 当 Query 中包含 order by 操作,而且无法利用索引完成的排序操作称为 “文件排序”。 |
Using join buffer | 改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。 |
Impossible where | 这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)。 |
Select tables optimized away | 这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行。 |
No tables used | Query语句中使用from dual 或不含任何from子句。 |
mysql> explain select * from student where id=12\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: const #只读取一次
possible_keys: PRIMARY
key: PRIMARY #使用了主键索引
key_len: 4
ref: const
rows: 1 #扫描了1条数据
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from student where name="wangwu"\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: student
partitions: NULL
type: ALL #全表扫描
possible_keys: NULL #没有使用索引字段
key: NULL #没有使用索引字段
key_len: NULL
ref: NULL
rows: 6 #扫描了6条数据,整个表就是6条数据
filtered: 16.67
Extra: Using where #在存储引擎检索行后再进行过滤
1 row in set, 1 warning (0.00 sec)
1.4.6 profile 工具
开启 profiling 设置可以记录 SQL 语句执行的详细过程
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
1 row in set, 1 warning (0.00 sec)
#无记录
mysql> show profiles;
Empty set, 1 warning (0.00 sec)
#开启
mysql> set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
#执行SQL语句
mysql> select * from student where name="wangwu";
+----+--------+------+--------+
| id | name | age | gender |
+----+--------+------+--------+
| 12 | wangwu | 13 | M |
+----+--------+------+--------+
1 row in set (0.00 sec)
#再次查看
mysql> show profiles;
+----------+------------+-------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-------------------------------------------+
| 1 | 0.00091900 | select * from student where name="wangwu" |
+----------+------------+-------------------------------------------+
1 row in set, 1 warning (0.00 sec)
#删除索引
mysql> drop index idx_name on student;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
#再次查询
mysql> select * from student where name="wangwu";
+----+--------+------+--------+
| id | name | age | gender |
+----+--------+------+--------+
| 12 | wangwu | 13 | M |
+----+--------+------+--------+
1 row in set (0.01 sec)
#两次用时对比
mysql> show profiles;
+----------+------------+-------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-------------------------------------------+
| 1 | 0.00091900 | select * from student where name="wangwu" |
| 2 | 0.01600600 | drop index idx_name on student |
| 3 | 0.00112750 | select * from student where name="wangwu" | #数据量太小,不能体现索引优势
+----------+------------+-------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
导入大表再次对比
[root@rocky86 ~]# cat testlog.sql
create table testlog (id int auto_increment primary key,name char(10),salary int default 20);
delimiter $$
create procedure sp_testlog()
begin
declare i int;
set i = 1;
while i <= 100000
do insert into testlog(name,salary) values (concat('wang',FLOOR(RAND() * 100000)),FLOOR(RAND() * 1000000));
set i = i +1;
end while;
end$$
delimiter;
#导入
[root@rocky86 ~]# mysql testdb < testlog.sql
mysql> use testdb;
Database changed
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| student |
| t1 |
| testlog |
+------------------+
3 rows in set (0.00 sec)
mysql> select count(*) from testlog;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.01 sec)
#执行存储过程
mysql> call sp_testlog;
Query OK, 1 row affected (1 min 2.02 sec)
mysql> select count(*) from testlog;
+----------+
| count(*) |
+----------+
| 100000 |
+----------+
1 row in set (0.01 sec)
#查询
mysql> select * from testlog limit 5;
+----+-----------+--------+
| id | name | salary |
+----+-----------+--------+
| 1 | wang64276 | 606675 |
| 2 | wang10506 | 705314 |
| 3 | wang21136 | 940888 |
| 4 | wang7034 | 529049 |
| 5 | wang43421 | 583940 |
+----+-----------+--------+
5 rows in set (0.00 sec)
#默认主键索引
mysql> show index from testlog\G;
*************************** 1. row ***************************
Table: testlog
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 98399
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.00 sec)
#查询
mysql> select * from testlog where salary=583940;
+----+-----------+--------+
| id | name | salary |
+----+-----------+--------+
| 5 | wang43421 | 583940 |
+----+-----------+--------+
1 row in set (0.01 sec)
#查看索引使用情况
mysql> explain select * from testlog where salary=583940\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: testlog
partitions: NULL
type: ALL #全表扫描
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 98399
filtered: 10.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
#查看执行时间
mysql> show profiles;
+----------+------------+-------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+-------------------------------------------+
| 1 | 0.01881575 | select * from testlog where salary=583940 |
+----------+------------+-------------------------------------------+
1 row in set, 1 warning (0.00 sec)
#在salary列创建索引
mysql> create index idx_salary on testlog(salary);
Query OK, 0 rows affected (0.16 sec)
Records: 0 Duplicates: 0 Warnings: 0
#再次查询
mysql> select * from testlog where salary=529049;
+----+----------+--------+
| id | name | salary |
+----+----------+--------+
| 4 | wang7034 | 529049 |
+----+----------+--------+
1 row in set (0.00 sec)
#对比
mysql> show profiles;
+----------+------------+--------------------------------------------+
| Query_ID | Duration | Query |
+----------+------------+--------------------------------------------+
| 1 | 0.01881575 | select * from testlog where salary=583940 |
| 2 | 0.15655150 | create index idx_salary on testlog(salary) |
| 3 | 0.00038900 | select * from testlog where salary=529049 |
+----------+------------+--------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
mysql> explain select * from testlog where salary=529049\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: testlog
partitions: NULL
type: ref
possible_keys: idx_salary
key: idx_salary
key_len: 5
ref: const
rows: 1 #只扫描1条记录
filtered: 100.00
Extra: NULL1 row in set, 1 warning (0.00 sec)