Day517.索引优化与查询优化 -mysql

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day517.索引优化与查询优化 -mysql相关的知识,希望对你有一定的参考价值。

索引优化与查询优化

一、数据准备

学员表50万 条, 班级表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 ;

#假如要删除
#drop function rand_string;

随机产生班级编号

#用于随机产生多少到多少的编号
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 ;

#假如要删除
#drop function rand_num;

步骤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 ;

#假如要删除
#drop PROCEDURE insert_stu;

创建往class表中插入数据的存储过程

#执行存储过程,往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 ;

#假如要删除
#drop PROCEDURE insert_class;

步骤5:调用存储过程
class

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

stu

#执行存储过程,往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");

二、索引失效案例

1、全值匹配我最爱

全值的等值匹配

2、最佳左前缀法则

拓展:Alibaba《Java开发手册》
索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

3、主键插入顺序


如果此时再插入一条主键值为 9的记录,那它插入的位置就如下图:


可这个数据页已经满了,再插进来咋办呢?我们需要把当前 页面分裂 成两个页面,把本页中的一些记录移动到新创建的这个页中。页面分裂和记录移位意味着什么?意味着: 性能损耗 !所以如果我们想尽量避免这样无谓的性能损耗,最好让插入的记录的 主键值依次递增 ,这样就不会发生这样的性能损耗了。
所以我们建议:让主键具有 AUTO_INCREMENT ,让存储引擎自己为表生成主键,而不是我们手动插入 ,比如: person_info 表:

CREATE TABLE person_info(
	 id INT UNSIGNED NOT NULL AUTO_INCREMENT,
	 name VARCHAR(100) NOT NULL,
	 birthday DATE NOT NULL,
	 phone_number CHAR(11) NOT NULL,
	 country varchar(100) NOT NULL,
	  PRIMARY KEY (id),
	  KEY idx_name_birthday_phone_number (name(10), birthday, phone_number)
);  

我们自定义的主键列 id 拥有 AUTO_INCREMENT 属性,在插入记录时存储引擎会自动为我们填入自增的主键值。这样的主键占用空间小,顺序写入,减少页分裂。

4、计算、函数、类型转换(自动或手动)导致索引失效

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

创建索引

CREATE INDEX idx_name ON student(NAME);

第一种:索引优化生效

mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
mysql>  SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
+---------+---------+--------+------+---------+
| id   | stuno  | name  | age | classId |
+---------+---------+--------+------+---------+
| 5301379 | 1233401 | AbCHEa |  164 |   259 |
| 7170042 | 3102064 | ABcHeB |  199 |   161 |
| 1901614 | 1833636 | ABcHeC |  226 |   275 |
| 5195021 | 1127043 | abchEC |  486 |    72 |
| 4047089 | 3810031 | AbCHFd |  268 |   210 |
| 4917074 |  849096 | ABcHfD |  264 |   442 |
| 1540859 |  141979 | abchFF |  119 |   140 |
| 5121801 | 1053823 | AbCHFg |  412 |   327 |
| 2441254 | 2373276 | abchFJ |  170 |   362 |
| 7039146 | 2971168 | ABcHgI |  502 |   465 |
| 1636826 | 1580286 | ABcHgK |  71 |   262 |
|  374344 |  474345 | abchHL |  367 |   212 |
| 1596534 |  169191 | AbCHHl |  102 |   146 |
         ...
| 5266837 | 1198859 | abclXe |  292 |   298 |
| 8126968 | 4058990 | aBClxE |  316 |   150 |
| 4298305 |  399962 | AbCLXF |  72 |   423 |
| 5813628 | 1745650 | aBClxF |  356 |   323 |
| 6980448 | 2912470 | AbCLXF |  107 |    78 |
| 7881979 | 3814001 | AbCLXF |  89 |   497 |
| 4955576 |  887598 | ABcLxg |  121 |   385 |
| 3653460 | 3585482 | AbCLXJ |  130 |   174 |
| 1231990 | 1283439 | AbCLYH |  189 |   429 |
| 6110615 | 2042637 | ABcLyh |  157 |    40 |
+---------+---------+--------+------+---------+
401 rows in set, 1 warning (0.01 sec)

第二种:索引优化失效

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

mysql>  SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
+---------+---------+--------+------+---------+
| id   | stuno  | name  | age | classId |
+---------+---------+--------+------+---------+
| 5301379 | 1233401 | AbCHEa |  164 |   259 |
| 7170042 | 3102064 | ABcHeB |  199 |   161 |
| 1901614 | 1833636 | ABcHeC |  226 |   275 |
| 5195021 | 1127043 | abchEC |  486 |    72 |
| 4047089 | 3810031 | AbCHFd |  268 |   210 |
| 4917074 |  849096 | ABcHfD |  264 |   442 |
| 1540859 |  141979 | abchFF |  119 |   140 |
| 5121801 | 1053823 | AbCHFg |  412 |   327 |
| 2441254 | 2373276 | abchFJ |  170 |   362 |
| 7039146 | 2971168 | ABcHgI |  502 |   465 |
| 1636826 | 1580286 | ABcHgK |  71 |   262 |
|  374344 |  474345 | abchHL |  367 |   212 |
| 1596534 |  169191 | AbCHHl |  102 |   146 |
         ...
| 5266837 | 1198859 | abclXe |  292 |   298 |
| 8126968 | 4058990 | aBClxE |  316 |   150 |
| 4298305 |  399962 | AbCLXF |  72 |   423 |
| 5813628 | 1745650 | aBClxF |  356 |   323 |
| 6980448 | 2912470 | AbCLXF |  107 |    78 |
| 7881979 | 3814001 | AbCLXF |  89 |   497 |
| 4955576 |  887598 | ABcLxg |  121 |   385 |
| 3653460 | 3585482 | AbCLXJ |  130 |   174 |
| 1231990 | 1283439 | AbCLYH |  189 |   429 |
| 6110615 | 2042637 | ABcLyh |  157 |    40 |
+---------+---------+--------+------+---------+
401 rows in set, 1 warning (3.62 sec)

type为“ALL”,表示没有使用到索引,查询时间为 3.62秒,查询效率较之前低很多。

再举例:

  • student表的字段stuno上设置有索引
CREATE INDEX idx_sno ON student(stuno);
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;

  • 索引优化生效:
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000;

再举例:

  • student表的字段name上设置有索引
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT id, stuno, name FROM student WHERE SUBSTRING(name, 1,3)='abc';

EXPLAIN SELECT id, stuno, NAME FROM student WHERE NAME LIKE 'abc%';


5、类型转换导致索引失效

下列哪个sql语句可以用到索引。(假设name字段上设置有索引)

# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;

# 使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';


name=123发生类型转换,索引失效。(隐式的类型转换)


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

ALTER TABLE student DROP INDEX idx_name;
ALTER TABLE student DROP INDEX idx_age;
ALTER TABLE student DROP INDEX idx_age_classid;
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
# student.classId>20的右侧的student.name = 'abc'的索引就会失效

create index idx_age_name_classid on student(age,name,classid);
  • 将范围查询条件放置语句最后:
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abc' AND student.classId>20 ;
#直接交换sql语句的位置是没有用的,需要改变联合索引的位置

7、 不等于(!= 或者<>)索引失效

当sql语句中有!=或者<>会出现索引失效的问题,尝试改写为等于,或采用覆盖索引

8、is null可以使用索引,is not null无法使用索引

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

9、 like以通配符%开头索引失效

拓展:Alibaba《Java开发手册》
【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

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

OR的前后条件都具备索引,如果缺少一个就会出现索引失效

# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;

#使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR name = 'Abel';

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

统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的 字符集 进行比较前需要进行 转换 会造成索引失效。


三、关联查询优化

1、数据准备

2、采用左外连接

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

使用函数优化 SQL 查询

Day782.HashMap的设计与优化 -Java 性能调优实战

greenplum 分区优化

day36-hibernate检索和优化 01-上次课内容回顾

在 presto 中优化窗口查询

oracle sql查询进一步优化1