MysqlSQL

MySQL-索引

2022-03-13  本文已影响0人  石头耳东

前置文章:
一、MySQL-Explain了解查询语句执行计划

零、本文纲要

tips:Ctrl + F快速定位到所需内容阅读吧。

一、索引概述

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。
1、索引特点

2、索引结构
数据结构演示网站:Data Structure Visualization (usfca.edu)

二、索引分类

聚集索引和二级索引.png

三、索引语法

CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ;
SHOW INDEX FROM table_name ;
DROP INDEX index_name ON table_name ;

简单练习

create table tb_user
(
    id         int auto_increment comment '主键'
        primary key,
    name       varchar(50)      not null comment '用户名',
    phone      varchar(11)      not null comment '手机号',
    email      varchar(100)     null comment '邮箱',
    profession varchar(11)      null comment '专业',
    age        tinyint unsigned null comment '年龄',
    gender     char             null comment '性别 , 1: 男, 2: 女',
    status     char             null comment '状态',
    createtime datetime         null comment '创建时间',
)
    comment '系统用户表';
INSERT INTO tb_user (name, phone, email, profession, age, gender, status,
createtime) VALUES ('妲己', '17799990015', '2783238293@qq.com', '软件工程', 31,'2', '0', '2001-01-30 00:00:00');

Ⅰ 创建常规索引
此处name因为实际场景中不是唯一的,可以重复,所以我们创建常规索引;

create index idx_user_name on tb_user(name);

Ⅱ 查看索引

show index from tb_user;
查看索引.png

Ⅲ 创建唯一索引
此处phone实际场景中是唯一的,所以我们创建唯一索引;

create unique index idx_user_phone on tb_user(phone);

Ⅳ 创建联合索引

create index idx_user_pro_age_sta on tb_user(profession,age,status);

四、SQL性能分析

使用SQL来显示状态:SHOW STATUS Statement
SHOW [GLOBAL | SESSION] STATUS提供服务器状态信息,该语句有一个变量Com_xxx是专门用来指示每个 xxx 语句的执行次数。

show global status like 'com_______';
-- 此处com后面有7个“_”下划线
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog    | 0     |
| Com_commit    | 0     |
| Com_delete    | 25    |
| Com_import    | 0     |
| Com_insert    | 38    |
| Com_repair    | 0     |
| Com_revoke    | 0     |
| Com_select    | 355   |
| Com_signal    | 0     |
| Com_update    | 26    |
| Com_xa_end    | 0     |
+---------------+-------+
mysql> show variables like "slow_query_log";
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | OFF   |
+----------------+-------+
-- 默认是关闭的

我们可以通过修改对应配置文件开启,位置/etc/my.cnf

vim /etc/my.cnf
# 插入下方数据
# 1表示开启,0表示关闭
slow_query_log=1
# 慢查询的设定时间10s,可以根据实际需求调整
long_query_time=10

查看慢SQL日志,位置/var/lib/mysql/localhost-slow.log

cat /var/lib/mysql/localhost-slow.log

官方文档:SHOW PROFILE Statement
SELECT @@profiling;查看profiling属性,默认关闭;
SET profiling = 1;开启profiling;
mysql> SHOW PROFILES;查看PROFILES详情,就是几个语句执行情况;
SHOW PROFILE FOR QUERY 1;查看具体某一个语句的执行情况;
SHOW PROFILE CPU FOR QUERY 2;带CPU使用情况的查询。

# 查看profiling属性,默认关闭
mysql> SELECT @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
mysql> SET profiling = 1;
mysql> DROP TABLE IF EXISTS t1;
mysql> CREATE TABLE T1 (id INT);
mysql> SHOW PROFILES;
+----------+----------+--------------------------+
| Query_ID | Duration | Query                    |
+----------+----------+--------------------------+
|        0 | 0.000088 | SET PROFILING = 1        |
|        1 | 0.000136 | DROP TABLE IF EXISTS t1  |
|        2 | 0.011947 | CREATE TABLE t1 (id INT) |
+----------+----------+--------------------------+
mysql> SHOW PROFILE FOR QUERY 1;
+--------------------+----------+
| Status             | Duration |
+--------------------+----------+
| query end          | 0.000107 |
| freeing items      | 0.000008 |
| logging slow query | 0.000015 |
| cleaning up        | 0.000006 |
+--------------------+----------+
mysql> SHOW PROFILE CPU FOR QUERY 2;
+----------------------+----------+----------+------------+
| Status               | Duration | CPU_user | CPU_system |
+----------------------+----------+----------+------------+
| checking permissions | 0.000040 | 0.000038 |   0.000002 |
| creating table       | 0.000056 | 0.000028 |   0.000028 |
| After create         | 0.011363 | 0.000217 |   0.001571 |
| query end            | 0.000375 | 0.000013 |   0.000028 |
| freeing items        | 0.000089 | 0.000010 |   0.000014 |
| logging slow query   | 0.000019 | 0.000009 |   0.000010 |
| cleaning up          | 0.000005 | 0.000003 |   0.000002 |
+----------------------+----------+----------+------------+

可以查看前置文章MySQL-Explain了解查询语句执行计划

五、索引使用

最左前缀法则指的是查询从联合索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)

mysql> explain select * from tb_user where profession = '软件工程';
mysql> explain select * from tb_user where profession = '软件工程' and status = 0;
后面的字段索引失效.png

以下场景由于使用了(>,<)范围查询,导致该索引后面的索引失效了。实际使用中像age>30等价于age>=31,所以我们可以替换一下。

mysql> explain select * from tb_user where profession = '软件工程' and age > 30 and status = '0';
mysql> explain select * from tb_user where profession = '软件工程' and age >= 31 and status = '0';
范围查询.png

以下案例关注| possible_keys | key |两列,possible_keys列为可能使用到的索引,key列是实际使用到的索引。

索引列上有计算或者使用函数,导致索引失效。

mysql> explain select * from tb_user where substring(phone,10,2) = '15';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   24 |   100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+

mysql>  explain select * from tb_user where id + 5 = 25;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   24 |   100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+

字符串不加引号,可以理解为MySQL底层做了数据类型转换,导致索引失效。

mysql> explain select * from tb_user where phone = '17799990015';
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table   | partitions | type  | possible_keys  | key            | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tb_user | NULL       | const | idx_user_phone | idx_user_phone | 46      | const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+-------+----------------+----------------+---------+-------+------+----------+-------+

mysql> explain select * from tb_user where phone = 17799990015;
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | idx_user_phone | NULL | NULL    | NULL |   24 |    10.00 | Using where |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+

在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字
前面加了%,索引将会失效。

mysql> explain select * from tb_user where profession like '%工程';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   24 |    11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+

mysql> explain select * from tb_user where profession like '软件%';
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys        | key                  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user | NULL       | range | idx_user_pro_age_sta | idx_user_pro_age_sta | 47      | NULL |    4 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+------+----------+-----------------------+

用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

mysql> explain select * from tb_user where id = 10 or age = 23;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL |   24 |    13.75 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+

MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描

mysql> explain select * from tb_user where phone >= '17799990005';
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user | NULL       | ALL  | idx_user_phone | NULL | NULL    | NULL |   24 |    79.17 | Using where |
+----+-------------+---------+------------+------+----------------+------+---------+------+------+----------+-------------+

mysql> explain select * from tb_user where phone >= '17799990015';
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys  | key            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user | NULL       | range | idx_user_phone | idx_user_phone | 46      | NULL |    9 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+

注意:is nullis not null是否走索引,也是根据实际效率来决定的。

mysql> explain select * from tb_user where profession = '软件工程';
mysql> create index idx_user_pro on tb_user(profession);
mysql> explain select * from tb_user where profession = '软件工程';
优化器选择.png

SQL提示,是优化数据库的一个重要手段,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

mysql> explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';
mysql> explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';
mysql> explain select * from tb_user force index(idx_user_pro_age_sta) where profession = '软件工程';
SQL提示.png

覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到

mysql> explain select id, profession from tb_user where profession = '软件工程' and age = 31 and status = '0' ;
mysql> explain select id,profession,age, status from tb_user where profession = '软件工程' and age = 31 and status = '0';
mysql> explain select id,profession,age, status, name from tb_user where profession = '软 件工程' and age = 31 and status = '0';
mysql> explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';
覆盖索引.png

① Extra
| Using where; Using index |:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据;
| Using index condition |:查找使用了索引,但是需要回表查询数据。

当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
语法:create index idx_xxxx on table_name(column(n)) ;
① 前缀长度
全列选择性:

SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;

某一长度前缀的选择性:

SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;

使用如下:

 select count(distinct email) / count(*) from tb_user ;
 select count(distinct substring(email,1,5)) / count(*) from tb_user ;
前缀索引选择.png

② 前缀索引的查询流程

前缀索引的查询流程.png

Ⅰ 根据sql语句的搜索项截取前缀
Ⅱ 拿着前缀在辅助索引内查找匹配项;
Ⅲ 从辅助索引拿到主键id
Ⅳ 根据主键id在聚集索引内找到对应row行数据
Ⅴ 辅助索引链表位置处继续向后确认,有则重复Ⅱ-Ⅴ;
Ⅵ 返回结果集。

在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
注意:如果MySQL未按预期执行,参考SQL提示,做适当调整。

单列索引与联合索引.png

六、索引设计原则

七、结尾

以上即为MySQL索引的部分内容,感谢阅读。

上一篇 下一篇

猜你喜欢

热点阅读