CREATE TABLE `dept` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR ( 30 ) DEFAULT NULL,
`address` VARCHAR ( 40 ) DEFAULT NULL,
`ceo` INT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
CREATE TABLE `emp` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL,
`name` VARCHAR ( 20 ) DEFAULT NULL,
`age` INT ( 3 ) DEFAULT NULL,
`deptId` INT ( 11 ) DEFAULT NULL,
PRIMARY KEY ( `id` )
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
# 创建函数,假如报错:This function has none of DETERMINISTIC......
# 由于开启过慢查询日志 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;
# 不加global只是当前窗口有效。mysqld重启,上述参数又会消失。
# 永久方法:
# linux下:/etc/my.cnf下my.cnf[mysqld]加上:log_bin_trust_function_creators=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;
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;
创建往 emp 表中插入数据的存储过程
DELIMITER $$
CREATE PROCEDURE insert_emp ( `start` INT, max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; # 设置手动提交事务
REPEAT # 循环
SET i = i + 1; # 赋值
INSERT INTO emp ( empno, NAME, age, deptid )
VALUES((`start` + i), rand_string ( 6 ), rand_num ( 30, 50 ), rand_num ( 1, 10000 ));
UNTIL i = max_num
END REPEAT;
COMMIT; # 提交事务
END $$
# 删除
DELIMITER;
DROP PROCEDURE insert_emp;
创建往 dept 表中插入数据的存储过程
# 执行存储过程,往 dept 表添加随机数据
DELIMITER $$
CREATE PROCEDURE `insert_dept` ( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname, address, ceo )
VALUES(rand_string ( 8 ), rand_string ( 10 ), rand_num ( 1, 500000 ));
UNTIL i = max_num
END REPEAT;
COMMIT;
END $$
# 删除
DELIMITER;
DROP PROCEDURE insert_dept;
# 执行存储过程,往 dept 表添加1万条数据
CALL insert_dept(10000);
# 执行存储过程,往 emp 表添加50万条数据
CALL insert_emp(100000,500000);
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 $$
# 执行存储过程
CALL proc_drop_index("tbname", "emp");
CALL proc_drop_index("tbname", "dept");
explain select sql_no_cache * from emp where emp.age=30 and deptid=4 and emp.name = 'abcd';
create index idx_age on emp(age);
create index idx_age_deptid on emp(age,deptid); # 包括上面的索引
create index idx_age_deptid_name on emp(age,deptid,NAME); # 包括上面两条索引,但不包括下面索引
create index idx_deptId_age on emp(deptId, age);
# 删除所有索引
create index idx_age_deptId_name on emp(age, deptId, `name`);
注:如果第一个字段都跳过了,那么整个复合索引都用不到了
# 创建索引
create index idx_name on emp(`name`);
# 索引生效
explain select sql_no_cache * from emp where emp.name like 'abc%';
# 索引失效
explain select sql_no_cache * from emp where left(emp.name,3) = 'abc';
# 创建索引
create index idx_age_deptId_name on emp(age, deptId, `name`);
# deptId 右边的列失效(不包括 deptId)
explain select sql_no_cache * from emp where emp.age=30 and emp.deptId>20 and emp.name = 'abc' ;
解决方法:
# 创建索引
create index idx_age_name_deptId on emp(age, `name`, deptId);
)]
explain select sql_no_cache * from emp where emp.name != 'abc';
# 创建索引
create index idx_age on emp(age);
# 索引生效
explain select sql_no_cache * from emp where age is null;
# 索引失效
explain select sql_no_cache * from emp where age is not null;
# 创建索引
create index idx_name on emp(`name`);
# 索引生效
explain select sql_no_cache * from emp where name like 'ab%';
# 索引失效
explain select sql_no_cache * from emp where name like '%ab%';
# 创建索引
create index idx_name on emp(`name`);
# 索引生效
explain select sql_no_cache * from emp where name = '123';
# 索引失效
explain select sql_no_cache * from emp where name = 123;
#分类
create table if not exists `class` (
`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`)
);
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(card) values(floor(1 + (rand() * 20)));
insert into class(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)));
# 添加索引优化
create index idx_book_card on book(card);
# 【右表被驱动表】,可以避免全表扫描
# 添加索引优化
create index idx_class_card class(card);
# 【左表驱动表】,无法避免全表扫描
优化思路:
# 先创建一个索引
create index idx_ceo on dept (ceo);
# ①不推荐
explain select * from emp a where a.id not in
(select b.ceo from dept b where b.ceo is not null);
# ②推荐
explain select a.* from emp a
left join dept b on a.id = b.ceo
where b.id is null;
注:不要使用 not in
或者 not exists
# 创建索引
create index idx_age_deptId_name on emp (age, deptId, `name`);
# 不使用索引
explain select sql_no_cache * from emp order by age,deptId;
# 使用索引
explain select sql_no_cache * from emp order by age,deptId limit 10;
在 where 和 order 同时存在的情况下,需要注意索引的顺序
# 创建索引
create index idx_emp_age_deptId_empno on emp (age, deptId, empno);
在 order 上出现两个方向相反的字段,会出现 Using file sort,文件排序,需要全盘扫描排序
# 创建索引
create index idx_age_deptId_name on emp (age, deptId, `name`);
# 删除所有索引(除主键外)
call proc_drop_index('test','emp');
# 需要优化的 sql
explain select sql_no_cache * from emp where age =30 and empno < 101000 order by `name`;
如果排序不在索引列上,就会使用 filesort 排序:mysql 就会使用双路排序和单路排序;
双路排序(又叫回表排序模式):先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段;MySQL 4.1 之前使用的就是双路排序;
单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;
举个例子:
select * from user where age = 30 order by name;
where age = 30
将所有满足age = 30
的行 ID 和name
字段绑定,放入 sort buffer 中;name
字段排序;注:单路排序虽然快,但是单路排序可能导致大量 I/O 操作。在 sort buffer 中单路要比双路排序占用很多空间,因为单路是把所有字段都取出, 所以有可能取出的数据的总大小超出了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据,进行排序(创建 tmp 文件,多路合并),排完再取 sort_buffer 容量大小,再排.....从而多次I/O。
Order by 时 select * 是一个大忌。只 Query 需要的字段, 这点非常重要。在这里的影响是:
尝试提高 sort_buffer_size
show variables like '%sort_buffer_size%';
尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。
SHOW VARIABLES LIKE ‘%max_length_for_sort_data%’; #默认1024字节
但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。如果需要返回的列的总长度大于 max_length_for_sort_data,使用双路算法,否则使用单路算法。1024-8192 字节之间调整;
group by 使用索引的原则几乎跟order by一致 ,唯一区别: