MySQL/MariaDB数据库的索引工作原理和优化
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
实际工作中索引这个技术是影响服务器性能一个非常重要的指标,因此我们得花时间去了解一下索引的相关特性。索引是一把双刃剑用得好可以快速查询数据,用得不好数据库查询速度不仅慢还降低服务器性能,浪费磁盘空间。
一.索引概述
1>.什么是索引
索引是特殊数据结构,定义在查找时作为查找条件的字段,在MySQL又称为键key,索引通过存储引擎实现。
索引可以看作是一本字典的目录,为了快速检索,用空间换时间,显著提高查询效率。
可以为一列或者多列字段设置索引。
不是说有了索引性能就一定能提升,有了索引咱们还得会利用索引,用正确的方法使用索引,使用不当反而会降低服务器性能。
2>.索引的优点
索引可以降低服务需要扫描的数据量,减少了IO次数
索引可以帮助服务器避免排序和使用临时表
索引可以帮助将随机I/O转为顺序I/O
3>.索引的缺点
占用额外空间,影响插入速度
二.索引类型
主键索引: 主键会自动建立主键索引,主键本身就是为了快速定位唯一记录的。 唯一索引: 表中的索引列组成的索引必须唯一,但可以为空,非空值必须唯一 普通索引: 没有唯一性的要求,就是建了一个字典的目录而已。 联合索引: 多个字段组合创建索引,使用条件查询时,先匹配左边字段 全文索引: MyISAM使用,对Char、Varchar、TEXT类型使用 空间索引,SPATIAL,基本不用。 聚簇(集)索引、非聚簇索引: 数据和索引是否存储在一起 稠密索引、稀疏索引: 是否索引了每一个数据项 简单索引、组合索引: 左前缀索引:取前面的字符做索引 覆盖索引:从索引中即可取出要查询的数据,性能高 其他索引: B+ TREE、HASH、R TREE
在MySQL中,InnoDB和MyISAM的索引数据结构可以使用Hash或BTree,innodb默认是BTree。 Hash时间复杂度是O(1),但是只能进行精确匹配,也就是Hash值的匹配,比如范围匹配就没办法了,
hash值无序所以无法知道原有记录的顺序。Hash问题较多。 BTree索引,以B+树为存储结构。
虽然,索引可以提高查询所读,但是却影响增删改的效率,因为需要索引更新或重构。频繁出现在 where子句中的列可以考虑使用索引。要避免把性别这种字段设索引。
1>.二叉树
(1)每个结点最多2棵子树 二叉树不存在度数大于2的结点。 (2)它是有序树,左子树,右子树是顺序的,不能交换次序 (3)即使某个节点只有一棵子树,也要确定它是左子树还是右子树 (4)二叉树的物种基本形态 1)空二叉树 2)只有一个根结点 3)根结点只有左子树 4)根结点只有右子树 5)根结点有左子树和右子树 博主推荐阅读: https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91 https://www.cnblogs.com/yinzhengjie/p/10960896.html
2>.红黑树(又叫自平衡二叉树)
博主推荐阅读: https://baike.baidu.com/item/%E7%BA%A2%E9%BB%91%E6%A0%91
3>.B-tree索引(B-tree(多路搜索树,并不是二叉的)
B-tree(多路搜索树,并不是二叉的)是一种常见的数据结构。使用B-tree结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。按照翻译,B 通常认为是Balance的简称。这个数据结构一般用于数据库的索引,综合效率较高。 博主推荐阅读: https://baike.baidu.com/item/B-tree/6606402?fr=aladdin
4>.B+树(MySQL数据库默认使用该索引)
B+树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。 B+树节点组织成一棵树。节点分为内部节点和叶子节点。 内部节点不存储数据,叶子节点不存储指针。
如下图所示,每个leaf node保存数据,所有的leaf node组织成链表。假设读取16到22的数据,找到18后,顺着链表 往后遍历读取即可。
InnoDB中,数据文件本身就是按主键索引存储的,叶子节点中保存的就是数据记录。 如果在其他字段上定义B+Tree索引,叶子节点的数据记录的是主键,这种称为辅助索引。 B+Tree索引: 顺序存储,每一个叶子节点到根结点的距离是相同的;左前缀索引,适合查询范围类的数据 可以使用B+Tree索引的查询类型: 全值匹配:精确所有索引列,如:姓yin,名zhengjie,年龄18 匹配最左前缀:即只使用索引的第一列,如:姓yin 匹配列前缀:只匹配一列值开头部分,如:姓以y开头的 匹配范围值:如:姓yin和姓ma之间 精确匹配某一列并范围匹配另一列:如:姓yin,名以x开头的 只访问索引的查询 B+Tree索引的限制: 如不从最左列开始,则无法使用索引,如:查找名为zhengjie,或姓为g结尾 不能跳过索引中的列:如:查找姓yin,年龄18的,只能使用索引第一列 如果查询中某个列是为范围查询,那么其右侧的列都无法再使用索引:如:姓yin,名z%,年龄18,只能利用姓和名上面的索引 特别提示: 索引列的顺序和查询语句的写法应相匹配,才能更好的利用索引 为优化性能,可能需要针对相同的列但顺序不同创建不同的索引来满足不同类型的查询需求 博主推荐阅读: https://baike.baidu.com/item/B+%E6%A0%91/7845683
5>.Hash索引
Hash索引: 基于哈希表实现,只有精确匹配索引中的所有列的查询才有效,索引自身只存储索引列对应的哈希值和数据指针,索引结构紧凑,查询性能好 Memory存储引擎支持显式hash索引,InnoDB和MyISAM存储引擎不支持 适用场景: 只支持等值比较查询,包括=, <=>, IN() 不适合使用hash索引的场景 不适用于顺序查询:索引存储顺序的不是值的顺序 不支持模糊匹配 不支持范围查询 不支持部分索引列匹配查找:如A,B列索引,只查询A列索引无效
6>.空间数据索引R-Tree( Geospatial indexing )
MyISAM支持地理空间索引,可以使用任意维度组合查询,使用特有的函数访问,常用于做地理数据存储,使用不多
InnoDB从MySQL5.7之后也开始支持
7>.全文索引(FULLTEXT)
在文本中查找关键词,而不是直接比较索引中的值,类似搜索引擎
InnoDB从MySQL 5.6之后也开始支持
8>.聚簇和非聚簇索引
9>.聚簇和非聚簇索引,主键和二级索引
10>.冗余和重复索引
冗余索引:(A),(A,B)
重复索引:已经有索引,再次建立索引
11>.索引优化策略
独立地使用列:
尽量避免其参与运算,独立的列指索引列不能是表达式的一部分,也不能是函数的参数,在where条件中,始终将索引列单独放在比较符号的一侧
左前缀索引:
构建指定索引字段的左侧的字符数,要通过索引选择性来评估
索引选择性:
不重复的索引值和数据表的记录总数的比值
多列索引:
AND操作时更适合使用多列索引,而非为每个列创建单独的索引
选择合适的索引列顺序:
无排序和分组时,将选择性最高放左侧
12>.索引优化建议
只要列中含有NULL值,就最好不要在此例设置索引,复合索引如果有NULL值,此列在使用时也不会使用索引
尽量使用短索引,如果可以,应该制定一个前缀长度
对于经常在where子句使用的列,最好设置索引
对于有多个列where或者order by子句,应该建立复合索引
对于like语句,以%或者‘-’开头的不会使用索引,以%结尾会使用索引
尽量不要在列上进行运算(函数操作和表达式操作)
尽量不要使用not in和<>操作
13>.SQL语句性能优化
查询时,能不要*就不用*,尽量写全字段名
大部分情况连接效率远大于子查询
多表连接时,尽量小表驱动大表,即小表 join 大表
在有大量记录的表分页时使用limit
对于经常使用的查询,可以开启缓存
多使用explain和profile分析查询语句
查看慢查询日志,找出执行时间长的sql语句优化
三.管理索引实战
1>.创建测试数据
MariaDB [yinzhengjie]> CREATE TABLE index_test (id INT auto_increment PRIMARY KEY,name CHAR(50),age INT DEFAULT 20); Query OK, 0 rows affected (0.01 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> DELIMITER $$ MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE PROCEDURE pro_testlog() -> BEGIN -> DECLARE i INT; -> SET i = 1; -> WHILE i < 100000 -> DO -> INSERT INTO index_test(name,age) VALUES (CONCAT('yinzhengjie',i),i); -> SET i = i +1; -> END WHILE; -> END$$ Query OK, 0 rows affected (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> DELIMITER ; MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SELECT * FROM index_test; Empty set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CALL pro_testlog; #调用存储过程,往index_test表中写入测试数据 Query OK, 1 row affected (34.07 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test LIMIT 10; +----+---------------+------+ | id | name | age | +----+---------------+------+ | 1 | yinzhengjie1 | 1 | | 2 | yinzhengjie2 | 2 | | 3 | yinzhengjie3 | 3 | | 4 | yinzhengjie4 | 4 | | 5 | yinzhengjie5 | 5 | | 6 | yinzhengjie6 | 6 | | 7 | yinzhengjie7 | 7 | | 8 | yinzhengjie8 | 8 | | 9 | yinzhengjie9 | 9 | | 10 | yinzhengjie10 | 10 | +----+---------------+------+ 10 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test GROUP BY id DESC LIMIT 10; +-------+------------------+-------+ | id | name | age | +-------+------------------+-------+ | 99999 | yinzhengjie99999 | 99999 | | 99998 | yinzhengjie99998 | 99998 | | 99997 | yinzhengjie99997 | 99997 | | 99996 | yinzhengjie99996 | 99996 | | 99995 | yinzhengjie99995 | 99995 | | 99994 | yinzhengjie99994 | 99994 | | 99993 | yinzhengjie99993 | 99993 | | 99992 | yinzhengjie99992 | 99992 | | 99991 | yinzhengjie99991 | 99991 | | 99990 | yinzhengjie99990 | 99990 | +-------+------------------+-------+ 10 rows in set (0.00 sec) MariaDB [yinzhengjie]>
2>.查看索引
MariaDB [yinzhengjie]> HELP SHOW INDEX Name: 'SHOW INDEX' Description: Syntax: SHOW {INDEX | INDEXES | KEYS} {FROM | IN} tbl_name [{FROM | IN} db_name] [WHERE expr] SHOW INDEX returns table index information. The format resembles that of the SQLStatistics call in ODBC. This statement requires some privilege for any column in the table. You can use db_name.tbl_name as an alternative to the tbl_name FROM db_name syntax. These two statements are equivalent: SHOW INDEX FROM mytable FROM mydb; SHOW INDEX FROM mydb.mytable; The WHERE clause can be given to select rows using more general conditions, as discussed in https://mariadb.com/kb/en/extended-show/. You can also list a table's indexes with the mysqlshow -k db_name tbl_name command. URL: https://mariadb.com/kb/en/show-index/ MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> DESC index_test; +-------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+----------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | char(50) | YES | | NULL | | | age | int(11) | YES | | 20 | | +-------+----------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_test; +------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+-- -------------+| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | I ndex_comment |+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+-- -------------+| index_test | 0 | PRIMARY | 1 | id | A | 99659 | NULL | NULL | | BTREE | | |+------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+-- -------------+1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test WHERE id = 99999; #由于主键就是索引,因此查询起来速度极快 +-------+------------------+-------+ | id | name | age | +-------+------------------+-------+ | 99999 | yinzhengjie99999 | 99999 | +-------+------------------+-------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test WHERE age = 99999; #不难发现,由于age并不是主键索引,因此查询起来速度明显有所降低 +-------+------------------+-------+ | id | name | age | +-------+------------------+-------+ | 99999 | yinzhengjie99999 | 99999 | +-------+------------------+-------+ 1 row in set (0.05 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test WHERE age = 99999; #很显然,查询的不是主键索引的列速度会明显下降,如果第二次调用该语句时查询时间为0,那就得观察缓存是否开启了。 +-------+------------------+-------+ | id | name | age | +-------+------------------+-------+ | 99999 | yinzhengjie99999 | 99999 | +-------+------------------+-------+ 1 row in set (0.04 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW VARIABLES LIKE 'query_cache%'; #查看缓存是否开启。 +------------------------------+---------+ | Variable_name | Value | +------------------------------+---------+ | query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 1048576 | | query_cache_strip_comments | OFF | | query_cache_type | OFF | | query_cache_wlock_invalidate | OFF | +------------------------------+---------+ 6 rows in set (0.00 sec) MariaDB [yinzhengjie]>
3>.创建索引
MariaDB [yinzhengjie]> HELP CREATE INDEX Name: 'CREATE INDEX' Description: Syntax: CREATE [ONLINE|OFFLINE] [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [index_type] ON tbl_name (index_col_name,...) [index_option] ... index_col_name: col_name [(length)] [ASC | DESC] index_type: USING {BTREE | HASH} index_option: KEY_BLOCK_SIZE [=] value | index_type | WITH PARSER parser_name | COMMENT 'string' CREATE INDEX is mapped to an ALTER TABLE statement to create indexes. See [HELP ALTER TABLE]. CREATE INDEX cannot be used to create a PRIMARY KEY; use ALTER TABLE instead. For more information about indexes, see https://mariadb.com/kb/en/optimization-and-indexes/. URL: https://mariadb.com/kb/en/create-index/ MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE INDEX my_index_test01 ON index_test(name(16)); Query OK, 0 rows affected (0.27 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test01 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM index_test GROUP BY id DESC LIMIT 3; +-------+------------------+-------+ | id | name | age | +-------+------------------+-------+ | 99999 | yinzhengjie99999 | 99999 | | 99998 | yinzhengjie99998 | 99998 | | 99997 | yinzhengjie99997 | 99997 | +-------+------------------+-------+ 3 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE name = 'yinzhengjie99999'; #我们发现该语句未用到任何索引。 +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | index_test | ALL | NULL | NULL | NULL | NULL | 99659 | Using where | +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE INDEX my_index_test02 ON index_test(name(16)); #紧接着我们创建一个在index_test表的name字段上创建一个前16个字符的索引. Query OK, 0 rows affected (0.27 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE name = 'yinzhengjie99999'; #我们发现该查询语句使用到咱们自己创建的索引啦。 +------+-------------+------------+------+-----------------+-----------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+------+-----------------+-----------------+---------+-------+------+-------------+ | 1 | SIMPLE | index_test | ref | my_index_test02 | my_index_test02 | 49 | const | 1 | Using where | +------+-------------+------------+------+-----------------+-----------------+---------+-------+------+-------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test02 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test02 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE INDEX my_index_03_name_age ON index_test(name,age); #创建复合索引 Query OK, 0 rows affected (0.34 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test02 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: *************************** 3. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_03_name_age Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: *************************** 4. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_03_name_age Seq_in_index: 2 Column_name: age Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 4 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE name = 'yinzhengjie10000'; #利用复合索引第一个字段查询,发现是可以利用到索引 +------+-------------+------------+------+--------------------------------------+----------------------+---------+-------+------+---------------- ----------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+------+-------------+------------+------+--------------------------------------+----------------------+---------+-------+------+---------------- ----------+| 1 | SIMPLE | index_test | ref | my_index_test02,my_index_03_name_age | my_index_03_name_age | 151 | const | 1 | Using where; Us ing index |+------+-------------+------------+------+--------------------------------------+----------------------+---------+-------+------+---------------- ----------+1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE age = '10000'; #利用复合索引第二个字段查询,发现也是可以利用到索引,但是不推荐,因为有的时候我们使用复合索引第二个字段查询是查询不到的。具体案例参考下面的演示。 +------+-------------+------------+-------+---------------+----------------------+---------+------+-------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+-------+---------------+----------------------+---------+------+-------+--------------------------+ | 1 | SIMPLE | index_test | index | NULL | my_index_03_name_age | 156 | NULL | 99659 | Using where; Using index | +------+-------------+------------+-------+---------------+----------------------+---------+------+-------+--------------------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test02 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> DESC students; +-----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------------------+------+-----+---------+----------------+ | StuID | int(10) unsigned | NO | PRI | NULL | auto_increment | | Name | varchar(50) | NO | | NULL | | | Age | tinyint(3) unsigned | NO | | NULL | | | Gender | enum('F','M') | NO | | NULL | | | ClassID | tinyint(3) unsigned | YES | | NULL | | | TeacherID | int(10) unsigned | YES | | NULL | | +-----------+---------------------+------+-----+---------+----------------+ 6 rows in set (0.01 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM students GROUP BY StuID DESC LIMIT 2; +-------+-----------------------+-----+--------+---------+-----------+ | StuID | Name | Age | Gender | ClassID | TeacherID | +-------+-----------------------+-----+--------+---------+-----------+ | 25 | 齐天大圣孙悟空 | 100 | M | NULL | NULL | | 24 | Xu Xian | 27 | M | NULL | NULL | +-------+-----------------------+-----+--------+---------+-----------+ 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE INDEX my_students_name_age ON students(name,age); #创建一个复合索引,但是注意使用索引时最好不要直接使用后面那个属性。 Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM students WHERE name = '齐天大圣孙悟空'; #我们使用复合索引的第一个name索引字段查询,发现执行SQL语句时可以利用到索引 +------+-------------+----------+------+----------------------+----------------------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+----------+------+----------------------+----------------------+---------+-------+------+-----------------------+ | 1 | SIMPLE | students | ref | my_students_name_age | my_students_name_age | 152 | const | 1 | Using index condition | +------+-------------+----------+------+----------------------+----------------------+---------+-------+------+-----------------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM students WHERE age = 100; #我们使用复合索引的第二个age索引字段查询,发现执行SQL语句时却不可以使用索引啦! +------+-------------+----------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+----------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | students | ALL | NULL | NULL | NULL | NULL | 25 | Using where | +------+-------------+----------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> DESC students; +-----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------------------+------+-----+---------+----------------+ | StuID | int(10) unsigned | NO | PRI | NULL | auto_increment | | Name | varchar(50) | NO | | NULL | | | Age | tinyint(3) unsigned | NO | | NULL | | | Gender | enum('F','M') | NO | | NULL | | | ClassID | tinyint(3) unsigned | YES | | NULL | | | TeacherID | int(10) unsigned | YES | | NULL | | +-----------+---------------------+------+-----+---------+----------------+ 6 rows in set (0.01 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SELECT * FROM students; +-------+-----------------------+-----+--------+---------+-----------+ | StuID | Name | Age | Gender | ClassID | TeacherID | +-------+-----------------------+-----+--------+---------+-----------+ | 1 | Shi Zhongyu | 22 | M | 2 | 3 | | 2 | Shi Potian | 22 | M | 1 | 7 | | 3 | Xie Yanke | 53 | M | 2 | 16 | | 4 | Ding Dian | 32 | M | 4 | 4 | | 5 | Yu Yutong | 26 | M | 3 | 1 | | 6 | Shi Qing | 46 | M | 5 | NULL | | 7 | Xi Ren | 19 | F | 3 | NULL | | 8 | Lin Daiyu | 17 | F | 7 | NULL | | 9 | Ren Yingying | 20 | F | 6 | NULL | | 10 | Yue Lingshan | 19 | F | 3 | NULL | | 11 | Yuan Chengzhi | 23 | M | 6 | NULL | | 12 | Wen Qingqing | 19 | F | 1 | NULL | | 13 | Tian Boguang | 33 | M | 2 | NULL | | 14 | Lu Wushuang | 17 | F | 3 | NULL | | 15 | Duan Yu | 19 | M | 4 | NULL | | 16 | Xu Zhu | 21 | M | 1 | NULL | | 17 | Lin Chong | 25 | M | 4 | NULL | | 18 | Hua Rong | 23 | M | 7 | NULL | | 19 | Xue Baochai | 18 | F | 6 | NULL | | 20 | Diao Chan | 19 | F | 7 | NULL | | 21 | Huang Yueying | 22 | F | 6 | NULL | | 22 | Xiao Qiao | 20 | F | 1 | NULL | | 23 | Ma Chao | 23 | M | 4 | NULL | | 24 | Xu Xian | 27 | M | NULL | NULL | | 25 | 齐天大圣孙悟空 | 100 | M | NULL | NULL | +-------+-----------------------+-----+--------+---------+-----------+ 25 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> CREATE UNIQUE INDEX my_unique_name_test03 ON students(name); #创建唯一键索引(该列字段不允许重复) Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM studentsG *************************** 1. row *************************** Table: students Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: StuID Collation: A Cardinality: 25 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: students Non_unique: 0 Key_name: _unique_name_test03 Seq_in_index: 1 Column_name: Name Collation: A Cardinality: 25 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> DESC students; +-----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------+---------------------+------+-----+---------+----------------+ | StuID | int(10) unsigned | NO | PRI | NULL | auto_increment | | Name | varchar(50) | NO | UNI | NULL | | | Age | tinyint(3) unsigned | NO | | NULL | | | Gender | enum('F','M') | NO | | NULL | | | ClassID | tinyint(3) unsigned | YES | | NULL | | | TeacherID | int(10) unsigned | YES | | NULL | | +-----------+---------------------+------+-----+---------+----------------+ 6 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM students WHERE name = '齐天大圣孙悟空'; +------+-------------+----------+-------+---------------------+---------------------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+----------+-------+---------------------+---------------------+---------+-------+------+-------+ | 1 | SIMPLE | students | const | _unique_name_test03 | _unique_name_test03 | 152 | const | 1 | | +------+-------------+----------+-------+---------------------+---------------------+---------+-------+------+-------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
4>.删除索引
MariaDB [yinzhengjie]> HELP DROP INDEX Name: 'DROP INDEX' Description: Syntax: DROP [ONLINE|OFFLINE] INDEX index_name ON tbl_name DROP INDEX drops the index named index_name from the table tbl_name. This statement is mapped to an ALTER TABLE statement to drop the index. See [HELP ALTER TABLE]. To drop a primary key, the index name is always PRIMARY, which must be specified as a quoted identifier because PRIMARY is a reserved word: DROP INDEX `PRIMARY` ON t; URL: https://mariadb.com/kb/en/drop-index/ MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: index_test Non_unique: 1 Key_name: my_index_test01 Seq_in_index: 1 Column_name: name Collation: A Cardinality: 99659 Sub_part: 16 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> DROP INDEX my_index_test01 ON index_test; #删除指定表中的索引 Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW INDEXES FROM index_testG *************************** 1. row *************************** Table: index_test Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: id Collation: A Cardinality: 99659 Sub_part: NULL Packed: NULL Null: Index_type: BTREE Comment: Index_comment: 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
4>.优化表空间
MariaDB [yinzhengjie]> HELP OPTIMIZE TABLE Name: 'OPTIMIZE TABLE' Description: Syntax: OPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL] TABLE tbl_name [, tbl_name] ... OPTIMIZE TABLE should be used if you have deleted a large part of a table or if you have made many changes to a table with variable-length rows (tables that have VARCHAR, VARBINARY, BLOB, or TEXT columns). Deleted rows are maintained in a linked list and subsequent INSERT operations reuse old row positions. You can use OPTIMIZE TABLE to reclaim the unused space and to defragment the data file. After extensive changes to a table, this statement may also improve performance of statements that use the table, sometimes significantly. This statement requires SELECT and INSERT privileges for the table. OPTIMIZE TABLE is supported for partitioned tables, and you can use ALTER TABLE ... OPTIMIZE PARTITION to optimize one or more partitions; for more information, see [HELP ALTER TABLE], and http://dev.mysql.com/doc/refman/5.5/en/partitioning-maintenance.html. URL: https://mariadb.com/kb/en/optimize-table/ MariaDB [yinzhengjie]>
5>.查看索引的使用
MariaDB [yinzhengjie]> SHOW VARIABLES LIKE 'userstat'; #查看索引的使用功能默认是关闭的 +---------------+-------+ | Variable_name | Value | +---------------+-------+ | userstat | OFF | +---------------+-------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> SET GLOBAL userstat=1; #临时开启该功能,也可以在配置文件中永久定义其开启,前提是咱们知道它是一个服务器参数。 Query OK, 0 rows affected (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> SHOW VARIABLES LIKE 'userstat'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | userstat | ON | +---------------+-------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> SHOW INDEX_STATISTICS; #查看哪些索引被利用了,哪些没有被利用。 +--------------+--------------+------------+-----------+ | Table_schema | Table_name | Index_name | Rows_read | +--------------+--------------+------------+-----------+ | mysql | tables_priv | PRIMARY | 1 | | mysql | columns_priv | PRIMARY | 2 | | yinzhengjie | index_test | PRIMARY | 1 | +--------------+--------------+------------+-----------+ 3 rows in set (0.00 sec) MariaDB [yinzhengjie]>
四.EXPLAIN
1>.EXPLAIN
通过EXPLAIN来分析索引的有效性
语法格式:EXPLAIN SELECT clause
获取查询执行计划信息,用来查看查询优化器如何执行查询
2>.案例演示
MariaDB [yinzhengjie]> HELP EXPLAIN Name: 'EXPLAIN' Description: Syntax: EXPLAIN [explain_type] SELECT select_options explain_type: EXTENDED | PARTITIONS Or: EXPLAIN tbl_name The EXPLAIN statement can be used either as a way to obtain information about how MySQL executes a statement, or as a synonym for DESCRIBE: o When you precede a SELECT statement with the keyword EXPLAIN, MySQL displays information from the optimizer about the query execution plan. That is, MySQL explains how it would process the statement, including information about how tables are joined and in which order. EXPLAIN EXTENDED can be used to obtain additional information. For information about using EXPLAIN and EXPLAIN EXTENDED to obtain query execution plan information, see https://mariadb.com/kb/en/explain/. o EXPLAIN PARTITIONS is useful only when examining queries involving partitioned tables. For details, see http://dev.mysql.com/doc/refman/5.5/en/partitioning-info.html. o EXPLAIN tbl_name is synonymous with DESCRIBE tbl_name or SHOW COLUMNS FROM tbl_name. For information about DESCRIBE and SHOW COLUMNS, see [HELP DESCRIBE], and [HELP SHOW COLUMNS]. URL: https://mariadb.com/kb/en/explain/ MariaDB [yinzhengjie]> MariaDB [yinzhengjie]>
MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE id = 99999; +------+-------------+------------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | index_test | const | PRIMARY | PRIMARY | 4 | const | 1 | | +------+-------------+------------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]> MariaDB [yinzhengjie]> EXPLAIN SELECT * FROM index_test WHERE age = 99999; +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | index_test | ALL | NULL | NULL | NULL | NULL | 99659 | Using where | +------+-------------+------------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.00 sec) MariaDB [yinzhengjie]>
3>.EXPLLAIN输出信息说明
id: 当前查询语句中,每个SELECT语句的编号 复杂类型的查询有三种: 简单子查询 用于FROM中的子查询 联合查询:UNION(注意:UNION查询的分析结果会出现一个额外匿名临时表) select_type: 简单查询为SIMPLE 复杂查询: SUBQUERY 简单子查询 PRIMARY 最外面的SELECT DERIVED 用于FROM中的子查询 UNION UNION语句的第一个之后的SELECT语句 UNION RESULT 匿名临时表 table: SELECT语句关联到的表 type: 关联类型或访问类型,即MySQL决定的如何去查询表中的行的方式,以下顺序,性能从低到高: ALL: 全表扫描 index:根据索引的次序进行全表扫描;如果在Extra列出现“Using index”表示了使用覆盖索引,而非全表扫描 range:有范围限制的根据索引实现范围扫描;扫描位置始于索引中的某一点,结束于另一点 ref: 根据索引返回表中匹配某单个值的所有行 eq_ref:仅返回一个行,但与需要额外与某个参考值做比较 const, system: 直接返回单个行 possible_keys: 查询可能会用到的索引 key: 查询中使用到的索引 key_len: 在索引使用的字节数 ref: 在利用key字段所表示的索引完成查询时所用的列或某常量值 rows: MySQL估计为找所有的目标行而需要读取的行数 Extra: 额外信息 Using index:MySQL将会使用覆盖索引,以避免访问表 Using where:MySQL服务器将在存储引擎检索后,再进行一次过滤 Using temporary:MySQL对结果排序时会使用临时表 Using filesort:对结果使用一个外部索引排序 博主推荐阅读: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html