Mysql详解——索引优化

MySQL 索引优化详解 当一个索引包含了查询所需的所有字段时,这个索引就被称为覆盖索引。在执行查询时,MySQL 可以直接从索引中获取结果,而不需要访问表数据。这不仅减少了 I/O 操作,还提高了查询效率。MySQL 索引优化是数据库性能优化的重要部分,通过合理使用索引、利用覆盖索引、以及掌握索引合并技术,可以显著提升查询性能。在设计索引时,既要考虑查询的频率和复杂度,也要兼顾写入性能的影响。在实际应用中,结合业务需求和查询模式,选择适当的索引优化策略,是提升 MySQL 数据库性能的关键。 阅读详情

本篇文章是对Mysql索引的创建以及优化进行一个介绍,关于索引的底层原理可以看我另一篇文章:Mysql详解——索引详解

文章目录

一、索引的创建和设计原则

1. 索引的声明和使用

1.1 索引的分类:
  • 从功能逻辑上讲,索引主要有四种:普通索引,唯一索引,主键索引,全文索引。

  • 按照物理实现方式,索引主要有两种:聚簇索引和非聚簇索引。

  • 按照作用字段个数进行划分:单列索引和多列索引。

1.2 索引的创建:

MySQL支持在单个或多个列上创建索引。

在创建表时创建索引:

隐式索引:主键,Unique修饰的字段,外键,MySQL都会自动这些字段创建索引。

CREATE TABLE dept(
    dept_id INT PRIMARY KEY AUTO_INCREMENT,
    dept_name VARCHAR(20)
);

CREATE TABLE emp(
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20) UNIQUE,
    dept_id INT,
    CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);

显示创建索引:

CREATE TABLE table_name [col_name data_type]
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]
  • UNIQUE 、 FULLTEXT 和 SPATIAL 为可选参数,分别表示唯一索引、全文索引和空间索引;
  • INDEX 与 KEY 为同义词,两者的作用相同,用来指定创建索引;
  • index_name 指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名;
  • col_name 为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择;
  • length 为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;
  • ASC 或 DESC 指定升序或者降序的索引值存储。

示例:

//创建普通索引
CREATE TABLE book(
    book_id INT ,
    book_name VARCHAR(100),
    authors VARCHAR(100),
    info VARCHAR(100) ,
    comment VARCHAR(100),
    year_publication YEAR,
    INDEX(year_publication)
);
//创建唯一索引
CREATE TABLE test1(
    id INT NOT NULL,
    name varchar(30) NOT NULL,
    UNIQUE INDEX uk_idx_id(id)
);
//创建组合索引
CREATE TABLE test3(
    id INT(11) NOT NULL,
    name CHAR(30) NOT NULL,
    age INT(11) NOT NULL,
    info VARCHAR(255),
    INDEX multi_idx(id,name,age)
);
在已经存在的表上创建索引:

在已经存在的表中创建索引可以使用 ALTER TABLE 语句或者 CREATE INDEX 语句。

ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY]
[index_name] (col_name[length],...) [ASC | DESC]
CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name
ON table_name (col_name[length],...) [ASC | DESC]
1.3 索引的删除:

使用ALTER TABLE删除索引:

ALTER TABLE table_name DROP INDEX index_name;

使用DROP INDEX语句删除索引:

DROP INDEX index_name ON table_name;

2. MySQL8.0中索引新特性

2.1 支持降序索引
CREATE TABLE ts1(
    a int,
    b int,
    index idx_a_b(a asc,b desc)
);

在某些时候我们可能需要对某个字段进行降序,这时候如果支持降序索引的话,将会大大提高查询效率。

测试:

分别在MySQL 5.7版本和MySQL 8.0版本的数据表ts1中插入800条随机数据,执行语句如下:

DELIMITER //
CREATE PROCEDURE ts_insert()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i < 800
    DO
        insert into ts1 select rand()*80000,rand()*80000;
        SET i = i + 1;
    END WHILE;
    commit;
END //
DELIMITER ;

#调用
CALL ts_insert();

在MySQL 5.7版本中查看数据表ts1的执行计划,结果如下:

EXPLAIN SELECT * FROM ts1 ORDER BY a,b DESC LIMIT 5;

从结果可以看出,执行计划中扫描数为799,而且使用了Using filesort。

Using filesort是MySQL中一种速度比较慢的外部排序,能避免是最好的。多数情况下,管理员可以通过优化索引来尽量避免出现Using filesort,从而提高数据库执行速度。

而在MySQL 8.0版本中查看数据表ts1的执行计划。从结果可以看出,执行计划中扫描数为5,而且没有使用Using filesort。

2.2 支持隐藏索引

在MySQL 5.7版本及之前,只能通过显式的方式删除索引。此时,如果发现删除索引后出现错误,又只能通过显式创建索引的方式将删除的索引创建回来。如果数据表中的数据量非常大,或者数据表本身比较大,这种操作就会消耗系统过多的资源,操作成本非常高。

从MySQL 8.x开始支持 隐藏索引,只需要将待删除的索引设置为隐藏索引,使查询优化器不再使用这个索引(即使使用force index(强制使用索引),优化器也不会使用该索引),确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。

这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除 。

隐藏索引的另一个好处就是可以通过通过设置隐藏索引的可见性可以查看索引对调优的帮助。

需要注意的是:当索引被隐藏时,它的内容仍然是和正常索引一样实时更新的。如果一个索引需要长期被隐藏,那么可以将其删除,因为索引的存在会影响插入、更新和删除的性能。

创建隐藏索引和创建普通索引一样,只不过多加了个INVISIBLE关键字。

CREATE TABLE tablename(
    propname1 type1[CONSTRAINT1],
    propname2 type2[CONSTRAINT2],
    ......
    propnamen typen,
    INDEX [indexname](propname1 [(length)]) INVISIBLE
);
ALTER TABLE tablename ADD INDEX indexname (propname [(length)]) INVISIBLE;
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; #切换成隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; #切换成非隐藏索引

在MySQL 8.x版本中,为索引提供了一种新的测试方式,**可以通过查询优化器的一个开关(use_invisible_indexes)来打开某个设置,使隐藏索引对查询优化器可见。**如果 use_invisible_indexes设置为off(默认),优化器会忽略隐藏索引。如果设置为on,即使隐藏索引不可见,优化器在生成执行计划时仍会考虑使用隐藏索引。

select @@optimizer_switch \G

在输出结果中可以看到 use_invisible_indexes=off

将隐藏索引设置成对查询优化器可见:

set session optimizer_switch="use_invisible_indexes=on";

3. 索引的设计原则

3.1 数据准备:
CREATE DATABASE atguigudb1;

USE atguigudb1;

#1.创建学生表和课程表
CREATE TABLE `student_info` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `student_id` INT NOT NULL ,
 `name` VARCHAR(20) DEFAULT NULL,
 `course_id` INT NOT NULL ,
 `class_id` INT(11) DEFAULT NULL,
 `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `course` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`course_id` INT NOT NULL ,
`course_name` VARCHAR(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
#函数1:创建随机产生字符串函数
DELIMITER //
CREATE FUNCTION rand_string(n INT) 
    RETURNS VARCHAR(255) #该函数会返回一个字符串
BEGIN 
    DECLARE chars_str VARCHAR(100) DEFAULT 
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    DECLARE return_str VARCHAR(255) DEFAULT '';
		DECLARE i INT DEFAULT 0;
    WHILE i < n DO 
       SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
       SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER 

#函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN   
DECLARE i INT DEFAULT 0;  
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1))   ;
RETURN i;  
END //
DELIMITER ;

这里有可能会报错,由于开启过慢查询日志bin-log, 我们就必须为我们的function指定一个参数。主从复制,主机会将写操作记录在bin-log日志中。从机读取bin-log日志,执行语句来同步数据。如果使用函数来操作数据,会导致从机和主键操作时间不一致。所以,默认情况下,mysql不开启创建函数设置。

查看MySQL是否允许创建函数:

show variables like 'log_bin_trust_function_creators';

通过命令行开启:

set global log_bin_trust_function_creators=1;

创建插入模拟数据的存储过程:

# 存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE  insert_course( max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
 SET autocommit = 0;    #设置手动提交事务
 REPEAT  #循环
 SET i = i + 1;  #赋值
 INSERT INTO course (course_id, course_name ) VALUES 
(rand_num(10000,10100),rand_string(6));  
 UNTIL i = max_num
 END REPEAT;  
 COMMIT;  #提交事务
END //
DELIMITER ;

# 存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE  insert_stu( max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
 SET autocommit = 0;    #设置手动提交事务
 REPEAT  #循环
 SET i = i + 1;  #赋值
 INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES 
(rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6));  
 UNTIL i = max_num  
 END REPEAT;  
 COMMIT;  #提交事务
END //
DELIMITER ;

调用存储过程:

CALL insert_course(100);

CALL insert_stu(1000000);
3.2 哪些情况适合创建索引
1. 字段值有唯一性的限制

如果表中某个字段是唯一的,即使是组合字段,强烈建议创建唯一性索引或者主键索引。

不要以为唯一索引影响了insert速度,这个速度是可以忽略的,但提高查询速度确实明显的。

2. 频繁作为WHERE查询条件的字段

某个字段在SELECT,UPDATE,DELETE语句中经常被使用到,尤其是在数据量大时,那么就需要为它建立索引了。

3. 经常GROUP BY和ORDER BY的列

索引本身就是让数据按照某种顺序进行存储或检索,因此对经常需要GROUP BY和ORDER BY的创建索引,能够大大提高查询效率。如果待排序有多个,那么可以考虑创建组合索引。

4. DISTINCT字段需要创建索引

有时候我们需要对某个字段进行去重,使用DISTINCT,那么对这个字段创建索引,也会提升查询效率。

这个也比较容易理解,通过创建索引,相同的数都挨在一起,那么去重也就更快了。

SELECT DISTINCT(student_id) FROM student_info
5. 多表 JOIN连接操作时,创建索引的注意事项

首先,在连接表时尽量不要超过3张,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询效率。

其次,对WHERE条件创建索引,因为WHERE才是对数据的过滤,在数据量大时没有索引是很可怕的。

最后,对连接的字段创建索引。

SELECT course_id, name, student_info.student_id, course_name 
FROM student_info JOIN course 
ON student_info.course_id = course.course_id
WHERE name = '462eed7ac6e791292a79';
6. 使用列的类型小的创建索引

这里说的类型小指的是该类型表示的数据范围的大小。

当我们要定义某一列的类型时,以整数类型为例,有TINYINT,MEDIUMINT,INT,BIGINT等,他们表示的范围依次递增,占用的空间也是依次递增。如果要对该列创建索引时,在数据范围允许的情况下,尽量选用较小的类型,也就是能用INT就不要用BIGINT,能够MEDIUMINT就不要用INT,原因如下:

  • 数据类型越小,在查询时进行的比较操作就越快。
  • 数据类型越小,索引占用的空间就越小,在一个数据页内就可以存放更多的记录,整颗B+树就更矮,从而减少IO次数。

这对于表的主键更加适用,因为不仅是聚簇索引还是非聚簇索引,各节点都会存放一份主键值,如果主键选用更小的类型,也就意味着节省更多的存储空间和更高效的IO。

7. 使用字符串的前缀创建索引

当我们为一个存储字符串的列创建索引时,如果字符串很长,那么会导致两个问题:

  • 字符串太长,在索引中占用的空间就越大。
  • 字符串越长,比较时花费的时间就越多。

因此我们可以通过截取字符串的前面一部分内容建立索引,这称为前缀索引。

这样在查找记录时虽然无法定位到具体的位置,但是可以定位到相应前缀的位置,然后根据前缀相同的主键值回表查询完整的字符串。即节约空间,又减少了字符串的比较时间,还大体解决排序问题。

alter table shop add index(address(12));

如何确定前缀的长度,截少了区分度不高,导致有很多数据要回表查询,截多了又达不到节约索引空间的目的。

先看一下字段在全部数据中的选择度:

select count(distinct address) / count(*) from shop

通过不同长度去计算,与全表的选择性对比

count(distinct left(列名, 索引长度))/count(*)

例如:

select count(distinct left(address,10)) / count(*) as sub10, -- 截取前10个字符的选择度
count(distinct left(address,15)) / count(*) as sub11, -- 截取前15个字符的选择度
count(distinct left(address,20)) / count(*) as sub12, -- 截取前20个字符的选择度
count(distinct left(address,25)) / count(*) as sub13 -- 截取前25个字符的选择度
from shop

在阿里巴巴开发手册中,强制要求对于varchar字段建立索引时必须指定索引长度,没必要对全字段建立索引。

一般来说,对字符串类型数据,长度为 20 的索引,区分度会高达90%以上 。

8. 区分度高的列作为索引

某一列的值中不重复个数越多,越适合作为索引,同时在联合索引时更适合放在前面。

可以使用select count(distinct a) / count(*) from t,计算区分度,一般超过33%就算是比较高的区分度了。

9. 使用最频繁的列放在联合索引的左侧

最左前缀原则。

10. 在多个列需要创建索引时,联合索引优于单值索引
3.3 限制索引的数目

在实际工作中,我们也需要平衡,索引的数目不是越多越好。建议每张表的索引数量不超过6个。原因:

  • 每个索引需要占用空间,索引越多,需要的磁盘空间就越多。
  • 索引会影响每次增删改的性能。
  • 优化器在选择如何查询优化时,如果有多个索引都可以用于查询,会增加MySQL优化器生成执行计划的时间,降低查询性能。
3.4 哪些情况不适合创建索引
1. 在where语句中用不到的条件,不要创建索引
2. 数据量小的表最好不要设置索引
3. 有大量重复数据的列上不要创建索引
4. 避免对经常需要更新的表创建过多的索引
5. 不建议用无序的值作为索引

例如身份证,UUID,由于这些都是无序的,在插入时不太可能按照顺序插入,导致插入的位置在中间,可能造成页分裂

6. 删除不再使用或者很少使用的索引
7. 不要定义冗余或者重复的索引
  • 冗余索引

    有时候可能无意对同一个列创建了多个索引,例如创建了索引index(a,b),后续又创建了索引index(a)。

  • 重复索引

    例如id本身是主键,我们又为id创建了个索引。

二、索引优化和查询优化

数据库优化的角度:

  • 索引失效,没有充分利用上索引 —— 建立索引
  • 关联查询太多JOIN(设计缺陷或者不得已的需求)——SQL优化,数据库添加冗余字段
  • 服务器调优及优化各个参数设置(缓冲,线程数等) —— 调整my.cnf
  • 数据过多 —— 分库分表

1. 数据准备

步骤1:建表

CREATE TABLE `class` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `className` VARCHAR(30) DEFAULT NULL,
 `address` VARCHAR(40) DEFAULT NULL,
 `monitor` INT NULL ,
 PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
CREATE TABLE `student` (
 `id` INT(11) NOT NULL AUTO_INCREMENT,
 `stuno` INT NOT NULL ,
 `name` VARCHAR(20) DEFAULT NULL,
 `age` INT(3) DEFAULT NULL,
 `classId` INT(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
 #CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

步骤2:允许创建函数

set global log_bin_trust_function_creators=1;    # 不加global只是当前窗口有效。

步骤3:创建函数

随机产生字符串
DELIMITER //
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN    
DECLARE chars_str VARCHAR(100) DEFAULT 
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO  
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));  
SET i = i + 1;
END WHILE;
RETURN return_str;
END //
DELIMITER 
#用于随机产生多少到多少的编号
DELIMITER //
CREATE FUNCTION  rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN   
DECLARE i INT DEFAULT 0;  
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1))   ;
RETURN i;  
END //
DELIMITER ;

步骤4:创建存储过程

#创建往stu表中插入数据的存储过程
DELIMITER //
CREATE PROCEDURE  insert_stu(  START INT ,  max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
 SET autocommit = 0;    #设置手动提交事务
 REPEAT  #循环
 SET i = i + 1;  #赋值
 INSERT INTO student (stuno, name ,age ,classId ) VALUES 
((START+i),rand_string(6),rand_num(1,50),rand_num(1,1000));  
 UNTIL i = max_num  
 END REPEAT;  
 COMMIT;  #提交事务
END //
DELIMITER ;
#执行存储过程,往class表添加随机数据
DELIMITER //
CREATE PROCEDURE `insert_class`(  max_num INT )
BEGIN  
DECLARE i INT DEFAULT 0;   
 SET autocommit = 0;    
 REPEAT  
 SET i = i + 1;  
 INSERT INTO class ( classname,address,monitor ) VALUES 
(rand_string(8),rand_string(10),rand_num(1,100000));  
 UNTIL i = max_num  
 END REPEAT;  
 COMMIT; 
 END //
DELIMITER ;

步骤5:调用存储过程

#执行存储过程,往class表添加1万条数据  
CALL insert_class(10000);

#执行存储过程,往stu表添加50万条数据  
CALL insert_stu(100000,500000);

步骤6:创建删除某表上的索引的存储过程

DELIMITER //
CREATE  PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
       DECLARE done INT DEFAULT 0;
       DECLARE ct INT DEFAULT 0;
       DECLARE _index VARCHAR(200) DEFAULT '';
       DECLARE _cur CURSOR FOR  SELECT   index_name   FROM 
information_schema.STATISTICS   WHERE table_schema=dbname AND table_name=tablename AND 
seq_in_index=1 AND    index_name <>'PRIMARY'  ;
#每个游标必须使用不同的declare continue handler for not found set done=1来控制游标的结束
       DECLARE  CONTINUE HANDLER FOR NOT FOUND set done=2 ;      
#若没有数据返回,程序继续,并将变量done设为2
        OPEN _cur;
        FETCH _cur INTO _index;
        WHILE  _index<>'' DO 
               SET @str = CONCAT("drop index " , _index , " on " , tablename ); 
               PREPARE sql_str FROM @str ;
               EXECUTE  sql_str;
               DEALLOCATE PREPARE sql_str;
               SET _index=''; 
               FETCH _cur INTO _index; 
        END WHILE;
   CLOSE _cur;
END //
DELIMITER ;

当想要删除索引时执行

CALL proc_drop_index("dbname","tablename");

2. 索引失效的案例

2.1 最佳左前缀法则

MySQL可以为多个字段创建索引,一个索引可以包含多个字段,对于多列索引,过滤条件要使用索引必须按照索引创建的顺序,一次满足,一旦跳过某个字段,索引后面的字段事实上并没有用上索引。

举例:创建联合索引

CREATE INDEX idx_name_age ON student(name,age);

查询,where中包含name和age:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name = 'abcd' AND student.age = 30;

结果:可以看到它使用上了联合索引

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vw8QXIk2-1650977379884)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220401235721620.png)]

查询:where中不包含name

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age = 30;

结果:可以看到由于匹配条件中没有name字段,不符合最左匹配选择,所以没有用上联合索引

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9darjrcX-1650977379887)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220401235636021.png)]

2.2 主键插入顺序

建议主键具有AUTO_INCREMENT(不严谨,对于某些场合不适用,具体看下面详细讲解),否则很有可能会出现新的值的插入位置在B+树的中间,并且所插入的数据页已经满了,这将导致需要将页分裂成两个页面,并且需要将原先页中的数据进行移动到新的页上,影响性能。

2.3 使用函数将导致索引失效

创建索引:

CREATE INDEX idx_name ON student(name);

两条查询语句:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geYJxoTr-1650977379888)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402000442344.png)]

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc'; 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1z4ikit-1650977379890)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402000506000.png)]

可以发现,没有使用函数的语句用上了索引,而使用了函数的语句没有用上索引

2.4 计算将导致索引失效

创建索引

CREATE INDEX idx_age ON student(age);

两条查询语句:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age = 31;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXot3nsq-1650977379891)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402001455355.png)]

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age - 1 = 30;	

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0qUpRsh6-1650977379893)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402001540938.png)]

可以发现,这两条语句所表达的意思其实是一样的,但是索引列有进行运算的话会导致索引失效。

2.5 类型转换将导致索引失效

查询条件中name字段(varchar类型)等于一个整数,此时会自动进行类型转换,导致索引失效。

 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name = 123

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1lF46kws-1650977379895)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402002231304.png)]

2.6 范围条件右边的列索引失效

创建索引:

create index idx_age_classid_name on student(age,classId,name);

执行查询语句:

EXPLAIN SELECT SQL_NO_CACHE * FROM student 
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;

结果发现,虽然用上了上面的索引,但是通过key_len发现该联合索引只用到了age和classId,并没有加上name,原因是因为在查询时classId是范围查找,导致后面列的索引失效了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W0T6oXjx-1650977379898)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402003212616.png)]

解决办法:创建新的索引,将classId和name的顺序调换

create index idx_age_name_classid on student(age,name,classId);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lg9cOnlT-1650977379900)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402003419601.png)]

此时发现,联合索引完全使用。

2.7 不等于会导致索引失效

如果索引列的条件是不等于(!= 或者 <>)的话,会导致该索引失效。

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name <> "abc";

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcNTlLEv-1650977379902)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402163027072.png)]

2.8 is null可以使用索引,is not null无法使用索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age IS NULL;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L9hbNvVe-1650977379903)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402163131802.png)]

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age IS NOT NULL;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bcx16l7k-1650977379904)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402163155801.png)]

可以看到,当索引列判断条件是is null时,是可以使用到索引的,当条件是is not null时,就无法使用上索引了。

2.9 LIKE以通配符%开头导致索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE '%abc'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JxdK8ePJ-1650977379906)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402163419654.png)]

2.10 OR前后存在非索引的列,索引失效

给age列创建索引:

CREATE INDEX idx_age ON student(age);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUPI3IMM-1650977379907)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402163610237.png)]

可以看到,虽然age有索引,但是由于classid没有索引,并且条件是通过OR连接起来的,因此导致索引失效。这个其实很容易理解,虽然age有索引,可以通过索引来进行查找,但是由于classid没有索引,因此也还是需要进行全表扫描,那最后就相当于要进行全表扫描,因此也就没必要使用age的索引了。

2.11 数据库和表的字符集统一使用utf8mb4

统一使用utf8mb4(5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。

不同的字符集进行比较前需要进行转换会造成索引失效。

3. 关联查询优化

3.1 数据准备
# 创建两张表
CREATE TABLE IF NOT EXISTS `type`(
	id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	card INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY(id)
)

CREATE TABLE IF NOT EXISTS `book`(
	bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	card INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY(bookid)
)
#分别插入20条数据
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));


INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
3.2 外连接

以左外连接为例:

EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RjHCfzLh-1650977379909)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402170507451.png)]

由于没有创建索引,因此这两个表的type都是all,并且rows都是20,这是由于联表查询事实上是一个笛卡儿乘积的过程,类似于两层嵌套的for循环

创建索引:

ALTER TABLE book ADD INDEX Y ( card);   #【被驱动表】,可以避免全表扫描
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmZWiRtw-1650977379910)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402170725486.png)]

可以看到第二行的type变成了ref,使用上了索引,并且rows变为了1。

这里想说的可能表达的不是很好,就是右边是我们的关键点,一定需要建立索引 ,因为左连接,左边是一定要全表扫描的,而右边是内层循环,只需要挑出符合左边的数据即可,因此右边建立索引可以大大提高检索效率。

以左连接为例,左边的表称为驱动表,右边的表称为被驱动表,我们要在被驱动表上加索引。

3.3 内连接

内连接和外连接不同的是:内连接两张表的地位相同,哪张为驱动表,哪张为被驱动表由优化器决定,很多情况下数据量少的为驱动表(驱动表要全表扫描,数据量少意味着扫描规模小)。

删除上面创建的索引:

drop index Y on book;
EXPLAIN  SELECT SQL_NO_CACHE * FROM  type INNER JOIN book ON type.card=book.card;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HLU6dCka-1650977379912)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402171734462.png)]

查询结果可以看到,在没有创建索引,默认情况下,优化器选择了type作为驱动表。

紧接着我们向type表中再插入20条数据:

INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO type(card) VALUES(FLOOR(1 + (RAND() * 20)));
.....

此时再查询:

EXPLAIN  SELECT SQL_NO_CACHE * FROM  type INNER JOIN book ON type.card=book.card;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCGano6N-1650977379913)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402171926124.png)]

可以发现此时这两条记录调换了,book为驱动表了,原因上面也说了,由于type新添加了20条数据,比book多了20条,所以优化器权衡之后觉得book数据量相对少点,作为全表扫描的驱动表更适合一点。

3.4 JOIN语句
1. Simple Nested-Loop Join

简单嵌套循环连接,算法很简单,也就是从表A中取出一条数据1,然后遍历表B,将匹配到的数据放到result中,以此类推。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yq1ZicbC-1650977379915)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402203233737.png)]

这种效率是非常低的。当然MySQL并不会这么粗暴去进行表的连接,所以就出现了后面的两种优化算法。

2. Index Nested-Loop Join

索引嵌套循环连接,其优化的思路利用索引减少内层表数据的匹配次数,所以要求被驱动表必须要有索引。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36XmJYLq-1650977379916)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402203754632.png)]

3. Block Nested-Loop Join

块嵌套循环连接。

如果没有用上索引,每次访问被驱动表,都需要将其表中的记录都加载到内存中,然后再从驱动表中取一条进行匹配,匹配结束后清除内存,然后再从驱动表中加载一条记录,然后将被驱动表的数据再次加载到内存中进行匹配。这样周而复始,大大浪费了IO的次数。

为了解决这个问题,获取驱动表中的数据,不再是一条一条地获取,而是一块一块地获取,引入了join buffer缓冲区,将驱动表中join相关的部分数据(大小受join buffer限制)缓冲到join buffer中,然后全表扫描被驱动表,将简单嵌套循环的多次比较合并成一次,降低了被驱动表的加载次数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGwTPNZK-1650977379918)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402204635640.png)]

参数设置:

  • block_nested_loop:通过show variables like '%optimizer_switch%' 查看其是否开启,默认是开启的。
  • join_buffer_size:设置join buffer的大小,默认是256K。

效率比较:INLJ > BNLJ > SNLJ

前面提到的小表驱动大表是不严谨的,应该是小的结果集驱动大的结果集。这里评判结果集大小是符合条件的表的行数 * 每行的大小。

例如有表A 连接 表B,表A有1000条数据,表B有100条数据,但是在where条件中过滤掉了表A很多数据,导致最终符合条件的表A只有50条数据,并且在join buffer中表A每行数据的大小 小于 表B每行数据的大小,那这时应该是表A驱动表B。

select t1.b, t2.* from t1 straight join t2 on (t1.b = t2.b);     #推荐

select t1.b, t2.* from t2 straight join t2 on (t1.b = t2.b);     #不推荐

对于上面的例子,由于t2要查询的字段是全部(假设t2有很多字段),而t1只查询一个字段,那么t1相较于t2每行数据占用的空间更少,t1作为驱动表的话,join buffer中能容纳更多的行数,因此应该让t1驱动t2。

因此从上面例子也可以总结,在查询时按需查询,减少不必要的字段的查询。

4. Hash JOIN

从MySQL8.0.20开始废弃了BNLJ(块嵌套循环连接),因为从MySQL8.0.18开始引入了Hash JOIN,并作为默认。

具体底层原理这里先挖个坑。

4. 子查询优化

子查询是MySQL的一项重要功能,可以帮助我们实现比较复杂的查询操作。但是,子查询一般效率都不高,原因如下:

  • **执行子查询时,MySQL需要为内层查询语句的查询结果创建一个临时表,**然后外层查询从临时表中再进行查询。临时表的创建和撤销,都会消耗CPU和IO资源,产生大量的慢查询。
  • 子查询产生的临时表,是不存在索引的,因此当临时表越大,对查询性能的影响也就越大。

因此,建议将子查询拆开成多条查询,或者使用 JOIN 来代替子查询。

5. 排序优化

在MySQL中,支持两种排序方式,分别是 FileSort 和 Index 排序:

  • Index排序中,索引可以保证数据的有序性,不再需要进行排序,效率更高。
  • **File Sort排序则一般是在内存中进行的,占用CPU较多。**如果待排结果较大,会产生临时文件IO到磁盘中进行排序的情况,效率较低。

优化建议:尽量对ORDER BY后的字段创建索引,这样可以避免File Sort。

接下来进行一些案例分析:

首先我们给age和classid创建索引

CREATE INDEX idx_age_classid_name ON student(age,classid,name)

进行查询:会发现索引失效

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age, classid 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p90Or5zq-1650977379919)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402221101282.png)]

加上limit 10,索引又能够用了

EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age, classid LIMIT 10;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvwP6jTI-1650977379920)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402221142344.png)]

原因:这其实是优化器进行权衡判断的结果,由于查询的是所有字段,这意味着需要进行回表操作,又因为表的数据很多,优化器认为通过索引查出来后再回表 并没有直接在内存中排序更快,因此选择不使用索引。

因此,如果将 * 改成 age,classid的话,就不用进行回表操作,那肯定还是用上索引更快。

EXPLAIN SELECT SQL_NO_CACHE age,classid FROM student ORDER BY age, classid;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-okTzNGTX-1650977379921)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220402221612003.png)]

FileSort算法

排序的字段如果不再索引列上,那么就会进行FileSort,FileSort有两种算法:双路排序 和 单路排序。

(1)双路排序(慢):

  • 在MySQL4.1之前是使用双路排序的,字面意思就是进行两次扫描磁盘。
  • 从磁盘中读取排序字段,然后在buffer中进行排序,然后按照排好的顺序再从磁盘中取其他字段。

从磁盘中进行两次IO操作,是很耗时的,因此出现了第二种改进的算法。

(2)单路排序(快):

从磁盘中读取查询需要的列,按照order by的字段在buffer中进行排序,最后进行输出。由于只需要进行一次IO,因此效率更快一点,但是需要更多的空间,因为它将每一行都保存在内存中了。

(3)优化策略:

  • 尝试提高 sort_buffer_size:

    增大buffer的容量,在MySQL5.7中,InnoDB存储引擎默认值是1MB

    SHOW VARIABLES LIKE '%sort_buffer_size%'
    
  • 尝试提高 max_length_for_sort_data:

    提高这个参数,会增加用改进算法的概率。

    SHOW VARIABLES LIKE '%max_length_for_sort_data%'
    

6. GROUP BY 优化

  • GROUP BY 使用索引的规则和 ORDER BY 大体一致。
  • 当无法使用索引列时,增加 max_length_for_sort_data 和 sort_buffer_size 参数能够有很好的效果。
  • where效率高于having,能写在where限定的条件就不要写在having中了。
  • 减少使用ORDER BY ,和业务沟通能不排序就不排序,或者将排序操作放到程序中解决。ORDER BY ,GROUP BY,DISTINCT这些语句很耗费CPU,数据库的CPU资源是很宝贵的。
  • 包含了ORDER BY,GROUP BY,DISTINCT的语句,where条件过滤出来的结果集尽量保持在1000行以内,否则SQL会很慢。

7. Limit 优化

假设此时有 LIMIT 2000000,10,这意味着MySQL需要排序前2000010条记录,然后仅仅返回2000000-2000010的记录,其他记录会被丢弃,查询排序的代价非常大。

优化思路一:利用主键索引查出2000000-2000010的记录的id,然后再回表查出其他列的内容,这同样也利用到了索引。

EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10) a 
WHERE t.id = a.id;

优化思路二:该方案适用于主键自增的表,可以把LIMIT查询转化成某个位置的查询(同样是利用索引)

EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;

8. 覆盖索引

如果索引中的值包含了我们想要查询的所有列,这个索引就叫做覆盖索引。

简单来说,索引列 + 主键 包含了SELECT 到 FROM 之间查询的列。

好处:

  • 避免InnoDB表进行索引的二次查询(回表)。
  • 可以将随机IO变成顺序IO加快查询效率。

缺点:

  • 索引字段的维护是需要代价的,因此,为了支持覆盖索引而建立冗余索引需要仔细权衡利弊。

这里来看一个例子:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age <> 20;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kJVQj7q-1650977379922)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403100509420.png)]

这是我们之前提到的关于不等于会导致索引失效,结结果也符合我们的预期,没有用上索引。

但当我们将查询列改成索引中有的值时,发现索引用上了。

EXPLAIN SELECT SQL_NO_CACHE age, name FROM student WHERE age <> 20;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0HXMDQa-1650977379923)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403100957674.png)]

因此,上面讲的索引失效的规则并不是绝对的,优化器会进行判断来决定最终是否使用索引。这里由于查询列是索引本身包含的值,因此优化器认为不需要回表,效率比直接查询整张表要高,就使用上了索引。

9. 索引下推

索引下推(Index Condition Pushdown),简称ICP,是MySQL5.6中的一个新特性,是一种在存储引擎层使用使用索引过滤数据的一种优化方式**。ICP可以减少回表次数。**

例如:

EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cf6L7UZb-1650977379925)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403105943395.png)]

可以看到Extra中显示Using index condition,这里就是指使用到了索引下推。

对于上面例子,key1 > ‘z’ 使用上了索引idx_key1,但是后面由于模糊查询是以%开头,所以无法使用上索引,但是存储引擎此时会进行一种优化:索引下推。也就是通过索引idx_key1查询出数据后,并不急着回表,而是先通过后面的模糊查询过滤掉不符合的数据,最后再回表查询所有的列数据,这就叫做索引下推。

如果没有索引下推,那么在通过索引 idx_key1查询出数据后,就立刻进行回表查出符合的数据,最后才判断是否 符合 LIKE ‘%a’ 的条件,这样增多了回表的次数。

对于联合索引也是如此:虽然name字段无法用上索引,但是存储引擎在回表之前会对该字段先进行过滤,减少回表时的数据量,提高查询效率。

EXPLAIN SELECT * FROM student WHERE age = 20 and classid = 10 and name LIKE '%a';

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LumhbjL-1650977379926)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403111021785.png)]

默认情况下ICP是开启的,可以通过设置 optimizer_swith 变量来控制:

#关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off';

#打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';

10. 其他优化策略

10.1 EXISTS 和 IN
SELECT * FROM A WHERE cc IN (SELECT cc FROM B)

SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc = A.cc) 

对于第一条语句使用的是IN,可以理解成会先查询B,也就是先查询子语句,然后将每一条查询结果去A表中找是否有符合条件的。因此适用于A的cc列有索引,并且B是小表。(换个角度理解,可以将 IN 看成 多个 = 号 )

对于第二条语句使用的是EXISTS,可以理解成会从A表中取出每一条数据,然后去B表中找是否有符合条件的。因此适用于B表的cc列有索引,并且A表是小表。

10.2 COUNT(*) 和 COUNT(具体字段)

区别:

  • 性能:
count(*) = count(1) > count(主键字段) > count(字段)
  • 对于NULL值是否统计:COUNT(*) 和 COUNT(1) 会计算NULL值,COUNT(具体字段)不会计算NULL值。

对于count(主键字段):

如果表里只有主键字段,没有二级索引时。InnoDB循环遍历聚簇索引,然后判断主键值是否为NULL,如果不为NULL就会被计数,知道所有的记录被读完。如果表里有二级索引,InnoDB循环遍历的对象就不是聚簇索引,而是二级索引,这是因为二级索引比主键索引占用更少的空间,能够减少IO次数。

对于count(1):

如果表里只有主键索引,没有二级索引时,InnoDB会循环遍历聚簇索引,但是不会读取记录的任何字段的值,也就是不会去判断是否为空,因此会说count(1)执行效率比count(主键字段)更高一点。

对于count(*):

count(*)其实等价于count(0),也就是说当你使用count(*),MySQL会自动将 * 转化成 0 来处理。所以,count(*)执行过程和count(1)执行过程基本一样,性能没有什么差异。

对于count(字段):

count(字段)的执行效率是最差的。对于下面这个查询来说,会采用全表扫描的方式来计数,这是很慢的查询效率。

如果非要统计该字段不为NULL的记录个数,建议给这个字段建立一个二级索引,这样就可以用上索引来统计。

EXPLAIN SELECT COUNT(gender) FROM student;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZHPm3AtA-1650977379927)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403152841441.png)]

前面讲的都是基于InnoDB存储引擎来说的,对于MyISAM存储引擎来说,执行count函数只需要O(1)复杂度,这是因为每张MyISAM的表都有一个meta信息存储了row_count值,由表级锁保证一致性,所以直接读取row_count的值就是count函数的执行结果了。

而由于InnoDB存储引擎是支持事务的,同一时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB应该返回多少行是不确定的,因此需要扫描表来统计具体的记录数。不过带上where条件语句后,MyISAM和InnoDB就没有区别了,都需要扫描表来进行统计。

当数据量很大时,count(*)是很耗时的,那么有什么办法可以进行优化吗?

  • 如果业务对于统计个数并不是需要很精确的话,比如搜索引擎在搜索关键词时给出的搜索结果数只是一个大概值,那么我们可以使用show table status 或者 explain 命令进行估算,执行explain命令效率是很高的,因为它并不会真正进行查询,下图中的rows就是 explain 命令对表student的记录数的估算。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRuIyX0D-1650977379930)(D:\File\笔记\成仙之路\Mysql\Mysql高级\assets\image-20220403155309925.png)]

  • 额外表保存计数值

    可以将计数值保存到另外一张表中,这样,在新增和删除时我们就需要进行相应的更新。

10.3 LIMIT 1

如果你确定结果集只有1条,那么加上LIMIT 1时,当找到一条结果后就不会再继续进行扫描了,能加快查询速度。 当然,如果本身对字段已经加上了唯一索引,那么也就不需要加上LIMIT 1了。

10.4 SELECT *

尽量不要使用SELECT *来查询:

  • MySQL在解析过程中,会通过数据字典将 * 按序转换成所有列名,这会大大浪费资源和时间。
  • 无法使用覆盖索引,需要进行回表。
10.5 多使用COMMIT

在程序中尽量进行COMMIT,这样程序的性能得到提高。

COMMIT所释放的资源有:

  • 回滚段上用于恢复数据的信息
  • 被程序语句获得的锁
  • redo / undo log buffer 中的空间
  • 管理上述3种资源中的内部花费

11. 主键该如何设置

UUID + 有序

参考淘宝订单

这里详细的有时间再整理。。挖坑。

Matlab实战:用卡尔曼滤波搞定无人机GPS轨迹优化(附完整代码) 本文详细介绍了如何使用Matlab实现卡尔曼滤波(KF)算法优化无人机GPS轨迹,提供完整代码和工程实践指南。通过线性卡尔曼滤波技术,有效降低定位噪声,提升轨迹平滑度,适用于无人机导航和控制系统开发。 阅读详情

相关推荐

新年美食鉴赏——基于注意力机制CBAM的美食101分类

新年美食鉴赏——基于注意力机制CBAM的美食101分类一、数据预处理1.数据集介绍2.读取标签3.统一命名4.整理图片路径5.划分训练集与验证集6.定义美食数据集二、注意力机制1.简介2.卷积块注意力网络——CBAM通道注意力模块CAM空间注意力模块SAM3.将通道、空间注意力模块合并三、基于卷积块注意力模块的ResNet-CBAM1.代码实现2.模型测试3.查看网络结构4.模型训练5.模型预测四、总结与升华个人简介 春节是一年当中最隆重、最盛大的节日,吃得最好的一餐可能就是年夜饭了,想想就很激动呢!随着新

zbp_12138的博客 1万+

MySQL索引优化(超详细)

MySQL索引优化,涉及到索引介绍,性能分析,查询优化,慢查询日志。

weixin_55816781的博客 9379

亚马逊广告接口 amazon advertising

亚马逊广告接口官方说明:https://advertising.amazon.com/API/docs/en-us/setting-up/account-setup 一:注册aws开发者账号 注册地址:https://developer.amazon.com 二:填写注册资料 三:进入控制台,生成安全配置文件 进入 Login with Amazon 菜单: 创建配置文件: 四:申请广告接入权限 申请地址:https://advertising.amazon.com/zh-cn/about-api

lzy66666的博客 4776

一文带你你搞懂索引如何优化!!!

前言索引的相信大家都听说过,但是真正会用的又有几人?平时工作中写SQL真的会考虑到这条SQL如何能够用上索引,如何能够提升执行效率?此篇文章详细的讲述了索引优化的几个原则,只要在工作中能...

码猿技术专栏 4175

浅谈python反序列化漏洞

0x01 python序列化与反序列化 Python 的序列化和反序列化是将一个类对象向字节流转化从而进行存储和传输,然后使用的时候再将字节流转化回原始的对象的一个过程。python中的序列化操作是通过pickle模块,而该模块主要包含l dumps将dict转化为str格式,loads将str格式转化为dict格式。 dump和load与文件操作结合起来, pickle.dump(obj, f...

Lethe's Blog 3220

Delta并联机构运动学正逆解:从理论建模到Matlab实战

本文深入解析了Delta并联机构运动学正逆解的核心原理与实战方法。通过几何建模,清晰阐述了其“逆解简单、正解复杂”的特性,并详细推导了基于一元二次方程的逆解解析算法与采用牛顿-拉弗森迭代法的正解数值算法。文章结合完整的Matlab代码实例,从符号推导到数值求解,提供了从理论到实践的完整实现路径,助力机器人开发者掌握Delta并联机构的运动学分析。

weixin_29096377的博客 432

优化索引的方法

为了避免索引隐式转换带来的性能问题,在编写 SQL 查询时,应注意查询条件中数据类型的匹配,尽量进行显式的类型转换,确保与字段的数据类型一致。由于索引通常需要在内存和磁盘中进行存储和处理,较小的索引字段大小意味着在一个索引页中能够容纳更多的索引值,从而提高索引的查询速度。联合索引按照字段在索引中的定义顺序进行匹配,如果查询条件的字段顺序与联合索引的顺序不一致,可能无法充分利用索引的优势。例如,对于一个包含很长字符串描述的字段,如文章的内容摘要,如果直接对整个字符串创建索引,会占用大量的空间。

lu_la_la_的博客 4132

《统计学习方法》——李航 学习大纲

最近在学习李航写的统计学习方法概论, 每一章都用xmind理清了思路,括号里是书里的公式,第一次写博文,敬请指教~~~~ 第一章 统计学习方法论 第二章 感知机 每个方法其实只需要着重掌握三要素和输入输出就可以了,主要看模型、策略和算法。 感知机主要是二类分类的线性分类模型。 看到后面会注意感知机和支持向量机的区别。 第三章 k近

wjy619251334的博客 1万+

项目中常用的MySQL优化你知道多少?

项目中常用的MySQL优化 文章目录项目中常用的MySQL优化前言一、mysql优化是什么?二、优化步骤1.EXPLAIN2.SQL语句中IN包含的值不应过3.SELECT语句务必指明字段名称4.如果排序字段没有用到索引,就尽量少排序5.如果限制条件中其他字段没有索引,尽量少用or6.尽量用union all代替union7.不使用ORDER BY RAND()8.区分in和exists, not in和not exists9.使用合理的分页方式以提高分页的效率10.分段查询11.不建议使用%前缀模糊查询1

qq_40500045的博客 1650

YOLO26 从零到工业落地全实战:自定义训练+多场景测试+边缘部署预备,踩坑实测100%可复现

很多人觉得训练是核心,其实不是——数据集的质量,直接决定了你模型的上限。我见过太多人,标注不规范、格式错误、数据集划分混乱,然后疯狂调参,训了几百轮还是不收敛,纯属白费功夫。我这次用的是工业零件缺陷检测自定义数据集,共3个类别:划痕、毛刺、变形,总计1200张图片,给大家讲一套标准的、可复现的数据集制作流程,不管你是做工业检测、安防、交通,都能直接套用。很多人拿到数据集,直接用默认命令训练,然后抱怨精度上不去。默认参数是给COCO数据集用的,你的自定义数据集,必须针对性调参,不然根本达不到最优效果。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 569

YOLOv8改进:注意力系列篇 |高效的通道先验卷积注意力(CPCA) | 中科院 2023.6

高效的通道先验卷积注意力(CPCA)方法,支持注意力权重在通道和空间维度上的动态分布;

会AI的学姐 2042

MYSQL 索引详解及索引优化、分析

索引在MySQL中是比较常见的,索引就相当于我们看书的目录,它是帮助MySQL高效获取数据的一种数据结构,主要用来提高数据的检索效率,减少IO成本,同时通过索引对数据进行排序,降低排序成本。2.索引的作用加快查询速度:通过索引快速定位数据。保证数据唯一性:如主键和唯一索引。优化排序和分组:索引可以加速ORDER BY和GROUP BY操作。用二级索引查数据时,会先检二级索引中的 B+Tree 的索引值(商品编码,product_no),找到对应的叶子节点,然后获取主键值,然后再通过主键索引。

2201_75656709的博客 1861

基于Simulink的鲸鱼优化算法(WOA)MPPT仿真

摘要:本文介绍了基于Simulink的鲸鱼优化算法(WOA)在光伏MPPT控制中的应用。WOA模拟座头鲸的"气泡网捕食策略",通过包围猎物、螺旋收缩和随机搜索三阶段动态平衡全局探索与局部开发能力,有效解决局部阴影导致的多峰P-V曲线问题。文章详细讲解了WOA-MPPT的Simulink建模过程,包括多峰光伏阵列建模、WOA控制器实现和Boost变换器控制。仿真结果显示,在三种典型工况下,WOA-MPPT的追踪效率>99.3%,动态响应时间<0.15s,性能优于传统算法。最后提出了混合WO

amy_mhd的博客 72

MySql索引优化详解

关于存储引擎的区别可见 Innodb跟Myisam的区别一般索引数据结构有以下几种: 二叉树、红黑树、Hash树、B-Tree。 二叉树、红黑树: Hash树: 对索引的key进行一次hash运算就可以定位出数据存储的位置。 很多时候Hash索引要比B+树索引更高效。 仅能满足 “=” 、“IN” ,不支持范围查询。 有hash冲突问题。 B-Tree: 叶节点具有相同的深度,叶节点的指针为空。 所有索引元素不重复。 节点中的数据索引从做到右递增排列。 Mysql一般采用B+树(B-tree变种): 非叶

weixin_42223521的博客 418

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

MySQL索引详解与优化策略

MySQL的索引是在存储引擎层实现的,不同的存储引擎支持的索引类型并不完全相同。例如,常见的InnoDB存储引擎支持B-tree索引,而Memory存储引擎则支持Hash索引。

m0_73355421的博客 1329

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

【MySql索引优化二详解】

文章主要探讨了MySQL中的索引优化策略,特别是分页查询和表关联查询的优化技巧。对于分页查询,若主键自增且连续,可以通过改写SQL语句减少扫描行数;若基于非主键字段排序,建议先查询主键再获取记录,以提高效率。在表关联查询中,MySQL常用Nested-Loop Join(NLJ)和Block Nested-Loop Join(BNL)两种算法。NLJ适用于被驱动表关联字段有索引的情况,而BNL则用于无索引场景,通过将驱动表数据加载到内存中减少磁盘I/O,尽管可能增加内存判断次数。优化策略强调根据实际情况选择

liwenhua的博客 875

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

MySQL Explain详解与索引优化实战

MySQL的EXPLAIN工具是SQL性能优化的关键,它能显示查询的执行计划而不实际执行SQL。该工具通过分析id、select_type、type等列信息,帮助开发者识别查询效率问题。重点包括:type列反映访问类型,应至少达到range级别;联合索引遵循最左前缀原则;避免在索引列上使用函数或计算;范围查询会导致右边列索引失效;LIKE以通配符开头会索引失效。MySQL 8.0优化器行为有所变化,需实际测试验证。建议开发者在编写SQL时使用覆盖索引、合理设计查询条件,并定期分析慢查询日志进行持续优化。

alonewolf_99的博客 810

【MySQL】Explain详解与索引优化实战

目录 1、使用的表 2、explain 中的列 2.1 id列 1)简单子查询 2)from子句中的子查询 3)union查询 2.2 select_type列 2.3 table列 2.4 type列 2.5 possible_keys列 2.6 key列 2.7 key_len列 2.8 ref列 2.9 rows列 2.10 Extra列 3、索引优化实战 ...

小七的博客 1486

[MySQL总结] Explain详解、索引最佳优化

like KK%相当于=常量,%KK和%KK% 相当于范围。

qq_45061342的博客 2031

MySQL性能调优——索引详解与索引的优化

——索引优化,可以说是数据库相关优化、理解尤其是查询优化中最常用的优化手段之一。所以,只有深入索引的实现原理、存储方式、不同索引间区别,才能设计或使用最优的索引,最大幅度的提升查询效率! 一、BTree索引 1、概述 Mysql数据库中使用最频繁的索引类型,基本所有存储引擎都支持BTree索引。正是其优异的检索表现,才使其有这样的地位。 2、存储结构 正如其名,这类索引的物理文件

Leeon的博客 3万+

mysql数据库索引详解,索引优化、分析

索引在MySQL中是比较常见的,索引就相当于我们看书的目录,它是帮助MySQL高效获取数据的一种数据结构,主要用来提高数据的检索效率,减少IO成本,同时通过索引对数据进行排序,降低排序成本。用二级索引查数据时,会先检二级索引中的 B+Tree 的索引值(商品编码,product_no),找到对应的叶子节点,然后获取主键值,然后再通过主键索引中的 B+Tree 树查询到对应的叶子节点,然后获取整行数据。这个过程叫「回表」,也就是说要查两个 B+Tree 才能查到数据。

xiaocai_6666的博客 596

mysql优化之索引优化详解

mysql优化之索引优化 1、索引是什么? 官网给出的答案是:索引(Index)是帮助MySQL高效获取数据的数据结构。其本质就是数据结构,排好序的快速查找的数据结构。 2、索引的数据结构 二叉树(二叉树在极端的情况下会变成一个链表(比方说添加的数据都是递增的)在这种情况下二叉树的查询速度是特别低的) 红黑树(红黑树是一种平衡二叉树,每一个节点的子节点都是2,当插入的节点个数都是递增的话,红黑树会通过自旋将自己维护成一个平衡的二叉树,但是会导致树的层级很高,需进行多次 I/O,而且IO的次数多少,取决于增

prefect_start的博客 599

2.索引:MySQL索引优化详解

索引是一种用于提高数据查询速度的数据结构,类似于书籍的目录。通过索引,数据库能够快速定位目标数据,而不需要全表扫描。提高查询效率:加快数据检索,降低查询的时间复杂度。优化排序:当查询需要排序时,索引可以提升性能。加速聚合查询:如COUNTSUM等聚合操作可以更高效。不过,索引虽然提升查询性能,但会增加写入操作的开销以及存储空间的使用。因此,合理设计索引至关重要。最左前缀索引(Leftmost Prefix Index)是MySQL在多列组合索引中遵循的一种匹配原则。

m0_72560900的博客 1054

mysql索引详解及优化

对索引其实本身有一定程度的研究,但是最近看了一些讲解索引的视频,发现还是有些知识盲区,所以干脆写一篇进行更好的总结 索引 索引是帮助MySQL高效获取数据的排好序的数据结构(容易忽略的点:排好序)(形象点就是教科书的目录) 索引存储在文件里(也就是说有IO操作) 索引结构: 这里说说在几种数据结构中,mysql为什么选择hash,B+Tree 1、二叉树 2、红黑树 3、hash 4、BTree 首先,如果数据没有索引,那么我们读取数据是这样的 上面我们发现读取数据特别耗时,那有没有比较节时的数据结构,

qq_22162093的博客 524

MySQL索引. ref_MySQL索引优化Explain详解

在日常工作中,我们会有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,些时我们常常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看。所以我们深入了解MySQL的基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪种策略...

weixin_42519772的博客 339

Mysql优化之Explain详解、索引最佳实践

使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈,在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是执行这条SQL。

Z_Lisa的博客 1万+
上一篇: Mysql详解——索引详解
下一篇: Mysql详解——InnoDB的数据存储结构
OYMN
博客等级 码龄7年 298粉丝 50原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值