文章目录
1️⃣ 数据库调优的场景
2️⃣数据准备
(1)建表
(2)设置参数
(3)创建函数
(4)创建存储过程
(5)调用存储过程
(6)删除某表上的索引
3️⃣索引失效的情况
3.1 全值匹配我最爱(索引最佳)
3.2 不遵守最左前缀匹配原则
3.3 不按照递增顺序插入主键
3.4 计算、函数、类型转换(自动或手动)导致索引失效
3.5 类型转换导致索引失效
3.6 范围条件右边的列索引失效
3.7 不等于(!= 或者 <>)索引失效
3.8 is null可以使用索引,is not null无法使用索引
3.9 like 以通配符 % 开头索引失效
3.10 OR前后存在非索引的列
3.11 数据库和表的字符集不匹配
4.索引一般性建议
1️⃣ 数据库调优的场景
上一篇mysql进阶优化篇,我们介绍了数据库的性能分析工具,知道了怎么发现数据库的性能问题,这一篇博客我们将介绍索引失效的10种情况及原理
先来了解下可能需要进行数据库优化的场景。
索引失效(按准则编写、调整sql)
没有充分利用到索引(建立索引)
关联查询太多的JOIN(JOIN查询性能与表的数目成指数相关,一般不超过三张,否则需要进行sql优化或者进行反范式化设计,增加必要的冗余)
服务器调优与各个参数的设置,如缓存、线程数等(修改my.conf)
数据过高(已经在软件层面充分调优,但仍不能面对高并发场景,可以考虑分表分库分散服务器压力)
接下来我们介绍下sql查询优化。虽然sql查询优化技术很多,但是大致不离 物理查查询优化 和 逻辑查询优化 两大块。
物理查询优化:通过索引和表连接方式进行优化
逻辑查询优化:通过sql语句的等价代换,实现数据库查询的优化。
2️⃣数据准备
学员表插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表中插入数据的存储过程
#创建往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表添加1万条数据
#执行存储过程,往class表添加1万条数据 CALL insert_class(10000);
往stu表添加50万条数据,这个时间会稍微有点长,请耐心等待哟。
#执行存储过程,往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
3️⃣索引失效的情况
这里我们以InnoDB的B+树的索引结构作为讲解的重点,讲解索引失效的案例(3.1讲解索引最佳的实践)。之所以会出现索引失效的情况,其实是因为我们的优化器经过了成本开销的计算,决定不用索引。用不用索引都是优化器说了算,Sql语句是否会使用索引,跟数据库版本、数据量和数据选择度都有关系。
3.1 全值匹配我最爱(索引最佳)
全值匹配可以充分的利用组合索引。
在没有建立索引时会进行数据查询速度会比较慢
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30; EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4; EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';
QL_NO_CACHE
表示不使用查询缓存。
下图是在没有创建索引的情况下,第一条sql的执行效果。其查询时间是0.048s。
下面建立下索引
CREATE INDEX idx_age ON student(age); CREATE INDEX idx_age_classid ON student(age,classId); CREATE INDEX idx_age_classid_name ON student(age,classId,NAME);
再次执行查询sql,就可以使用到索引idx_age。并且其查询耗时会变短为0.024s。
执行如下sql。选择的索引则是:idx_age_classid。思考下为什么?
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4;
这是因为我们在构建索引idx_age_classid的B+树时,会先按照age排序,在按照calssId排序,对于这个sql来说,更加高效。
但是上面的索引可能不生效哦,在数据量较大的情况下,我们进行全值匹配SELECT *,优化器可能经过计算发现,我们使用索引查询所有的数据后,还需要对查找到的数据进行回表操作,性能还不如全表扫描。这里我们没有造这么多数据,所以就不演示效果咯。