# 修改加密规则 ALTER USER 'root'@'localhost' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER; # 更新一下用户的密码 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456'; #刷新权限 FLUSH PRIVILEGES;
ALTER USER 'xiamu' IDENTIFIED BY '123456' PASSWORD EXPIRE NEVER; ALTER USER 'xiamu' IDENTIFIED WITH mysql_native_password BY '123456'; FLUSH PRIVILEGES;
select user, host from mysql.user;
update mysql.user set host = '%'where user = 'root'; FLUSH PRIVILEGES;
CREATE TABLE mytbl2(id INT,NAME VARCHAR(16),age INT,dept INT); INSERT INTO mytbl2 VALUES(1,'zhang3',33,101); INSERT INTO mytbl2 VALUES(2,'li4',34,101); INSERT INTO mytbl2 VALUES(3,'wang5',34,102); INSERT INTO mytbl2 VALUES(4,'zhao6',34,102); INSERT INTO mytbl2 VALUES(5,'tian7',36,102);
#查询每个部门年龄最大的人 select name,dept,max(age) from mytbl2 group by dept; # ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'xxxx.mytbl2.NAME' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
# 在当前会话关闭sql_mode SET SESSION sql_mode = '';
# 再次查询, 此时的name会显示错误, 但会不报错了 select name,dept,max(age) from mytbl2 group by dept;
正确的语句: SELECT m.* FROM mytbl2 m INNER JOIN(SELECT dept,MAX(age) maxage FROM mytbl2 GROUP BY dept) AS ab ON ab.dept=m.dept AND m.age=ab.maxage
设置当前窗口中设置sql_mode
1 2 3
设置当前窗口中设置sql_mode SET GLOBAL sql_mode = 'modes...'; SET SESSION sql_mode = 'modes...';
## 查看这张空表的大小 [root@iZf8z0puua6p4won7xep5rZ dbtest1]# ll total 332 -rw-r----- 1 mysql mysql 2522 Sep 6 19:43 csv_demo_374.sdi -rw-r----- 1 mysql mysql 35 Sep 6 20:21 csv_demo.CSM -rw-r----- 1 mysql mysql 27 Sep 6 19:49 csv_demo.CSV -rw-r----- 1 mysql mysql 114688 Sep 8 10:34 emp1.ibd -rw-r----- 1 mysql mysql 114688 Sep 8 10:32 emp2.ibd -rw-r----- 1 mysql mysql 114688 Sep 8 11:05 mytest.ibd -rw-r----- 1 mysql mysql 114688 Sep 8 15:13 temp.ibd
SHOW CREATE TABLE ts1; /* CREATE TABLE `ts1` ( `a` int DEFAULT NULL, `b` int DEFAULT NULL, KEY `idx_a_b` (`a`,`b` DESC) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci */
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();
SELECT COUNT(*) FROM ts1;
# 优化测试 EXPLAIN SELECT * FROM ts1 ORDER BY a, b DESC LIMIT 5; /* mysql> EXPLAIN SELECT * FROM ts1 ORDER BY a, b DESC LIMIT 5\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: ts1 partitions: NULL type: index possible_keys: NULL key: idx_a_b key_len: 10 ref: NULL rows: 5 filtered: 100.00 Extra: Using index 1 row inset, 1 warning (0.01 sec) */
# 不推荐 EXPLAIN SELECT * FROM ts1 ORDER BY a DESC, b DESC LIMIT 5; /* mysql> EXPLAIN SELECT * FROM ts1 ORDER BY a DESC, b DESC LIMIT 5\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: ts1 partitions: NULL type: index possible_keys: NULL key: idx_a_b key_len: 10 ref: NULL rows: 799 filtered: 100.00 Extra: Using index; Using filesort 1 row inset, 1 warning (0.00 sec) */
SET SESSION optimizer_switch="use_invisible_indexes=on"; /* index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=off,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on,subquery_to_derived=off,prefer_ordering_index=on,hypergraph_optimizer=off,derived_condition_pushdown=on */ EXPLAIN SELECT * FROM book7 WHERE year_publication = '2022';
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 ;
# 报错的解决方法 SHOW VARIABLES LIKE 'log_bin_trust_function_creators'; SET GLOBAL log_bin_trust_function_creators = ON;
#函数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 ;
# 存储过程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 ;
# (2) 频繁作为 WHERE 查询条件的字段 # 查询当前 student_info 表中的索引 SHOW INDEX FROM student_info;
# student_id 字段上没有索引的: SELECT course_id, class_id, NAME, create_time, student_id FROM student_info WHERE student_id = 123110; # 325ms
# 添加索引, 需要花费一定时间 ALTER TABLE student_info ADD INDEX idx_sid(student_id);
# student_id 字段上有索引的: SELECT course_id, class_id, NAME, create_time, student_id FROM student_info WHERE student_id = 123110; # 118ms # 这个案例中可见, 给 student_id 添加了索引之后使用 where 进行查询, 速度快了将近三倍
# (3) 经常 GROUP BY 和 ORDER BY 的列
SHOW INDEX FROM student_info;
# student_id 字段上有索引的: SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id LIMIT 100; #68ms
# 删除 idx_sid 索引 DROP INDEX idx_sid ON student_info;
# student_id 字段上没有索引的: SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id LIMIT 100; #709ms
# 再测试 SHOW INDEX FROM student_info;
# 添加单列索引 ALTER TABLE student_info ADD INDEX idx_sid(student_id);
SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id ORDER BY create_time DESC LIMIT 100; #4.308s
# 修改sql_mode # select @@sql_mode;
# 添加联合索引 ALTER TABLE student_info ADD INDEX idx_sid_cre_time(student_id, create_time DESC);
SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id ORDER BY create_time DESC LIMIT 100; #0.352s
# 再进一步: ALTER TABLE student_info ADD INDEX idx_cre_time_sid(create_time DESC, student_id);
DROP INDEX idx_sid_cre_time ON student_info;
SHOW INDEX FROM student_info;
SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id ORDER BY create_time DESC LIMIT 100; #3.374s
# (4) UPDATE, DELETE 的 WHERE 条件列
# 先查看一下是否有跟 name 相关的索引 SHOW INDEX FROM student_info;
UPDATE student_info SET student_id = 10002 WHERE NAME = '462eed7ac6e791292a79'; # 0.859 s
# 添加 name 索引 ALTER TABLE student_info ADD INDEX idx_name(NAME);
UPDATE student_info SET student_id = 10001 WHERE NAME = '462eed7ac6e791292a79'; # 0.119 s
# (5) DISTINCT 字段需要创建索引
# 查看索引 SHOW INDEX FROM student_info;
# 删除 student_id 索引 DROP INDEX idx_sid ON student_info;
SELECT DISTINCT(student_id) FROM student_info; # 0.100s 0.51 0.101
# 添加student_id 索引
ALTER TABLE student_info ADD INDEX idx_sid(student_id DESC);
SELECT DISTINCT(student_id) FROM student_info; # 0.194s 0.70 0.094
SELECT COUNT(*) FROM student_info
# 这里最后一次测试是采用的降序索引, 可能是随机生成偏大的值更多一些
# (6) 多表 JOIN 连接操作时,创建索引注意事项
# 首先, 连接表的数量尽量不要超过 3 张 ,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。 # 其次, 对 WHERE 条件创建索引 ,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下,没有 WHERE 条件过滤是非常可怕的。 # 最后, 对用于连接的字段创建索引 ,并且该字段在多张表中的 类型必须一致 。比如 course_id 在student_info 表和 course 表中都为 int(11) 类型,而不能一个为 int 另一个为 varchar 类型。
SHOW INDEX FROM student_info;
# 此时查询是没有索引的 SELECT s.course_id, NAME, s.student_id, c.course_name FROM student_info s JOIN course c ON s.course_id = c.course_id WHERE NAME = '462eed7ac6e791292a79'; # 0.109s
# 创建索引 CREATE INDEX idx_sid ON student_info(student_id);
# 再次查询 SELECT s.course_id, NAME, s.student_id, c.course_name FROM student_info s JOIN course c ON s.course_id = c.course_id WHERE NAME = '462eed7ac6e791292a79'; # 0.044s
# (7) 使用列的类型小的创建索引
# (8) 使用字符串前缀创建索引
# (9) 区分度(散列值高)的列适合作为索引
# (10) 使用最频繁的列放到联合索引的左侧
SELECT * FROM student_info WHERE student_id = 10013 AND count_id = 100;
mysql> show status like 'uptime'; ERROR 2013 (HY000): Lost connection to MySQL server during query No connection. Trying to reconnect... Connection id: 8 Current database: *** NONE ***
## 创建存储过程 DELIMITER // CREATE PROCEDURE insert_stu1( 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(10,100),rand_num(10,1000)); UNTIL i = max_num END REPEAT; COMMIT; #提交事务 END // DELIMITER ;
Parse and summarize the MySQL slow query log. Options are
--verbose verbose --debug debug --help write this text to standard output
-v verbose -d debug -s ORDER what to sort by (al, at, ar, c, l, r, t), 'at' is default al: average lock time ar: average rows sent at: average query time c: count l: lock time r: rows sent t: query time -r reverse the sort order (largest last instead of first) -t NUM just show the top n queries -a don't abstract all numbers to N and strings to 'S' -n NUM abstract numbers with at least n digits within names -g PATTERN grep: only consider stmts that include this string -h HOSTNAME hostname of db server for *-slow.log filename (can be wildcard), default is '*', i.e. match all -i NAME name of server instance (if using mysql.server startup script) -l don't subtract lock time from total time
[root@iZf8z0puua6p4won7xep5rZ bin]# /www/server/mysql/bin/mysqldumpslow -s t -t 5 /www/server/data/mysql-slow.log
Reading mysql slow query log from /www/server/data/mysql-slow.log Count: 1 Time=282.91s (282s) Lock=0.00s (0s) Rows=0.0 (0), root[root]@localhost CALL insert_stu1(N,N)
Count: 1 Time=4.23s (4s) Lock=0.00s (0s) Rows=100.0 (100), root[root]@[14.150.187.223] select student_id, count(*) as num from student_info group by student_id order by create_time desc limit N
Count: 1 Time=3.28s (3s) Lock=0.00s (0s) Rows=100.0 (100), root[root]@[14.150.187.223] SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id ORDER BY create_time DESC LIMIT N
Count: 1 Time=4.23s (4s) Lock=0.00s (0s) Rows=100.0 (100), root[root]@[14.150.187.223] select student_id, count(*) as num from student_info group by student_id order by create_time desc limit 100
Count: 1 Time=3.28s (3s) Lock=0.00s (0s) Rows=100.0 (100), root[root]@[14.150.187.223] SELECT student_id, COUNT(*) AS num FROM student_info GROUP BY student_id ORDER BY create_time DESC LIMIT 100
mysql> show variables like '%long_query_time%'; ERROR 2013 (HY000): Lost connection to MySQL server during query No connection. Trying to reconnect... Connection id: 8 Current database: atguigudb1
EXPLAIN DELETE FROM student_info WHERE id = 2; # 说明了 EXPLAIN 仅仅只是查看了这个执行计划, 并没有真正的执行
# DESCRIBE 跟 EXPLAIN 效果一样 DESCRIBE DELETE FROM student_info WHERE id = 2;
USE atguigudb1;
## 数据准备 CREATE TABLE s1 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2 (key2), INDEX idx_key3 (key3), INDEX idx_key_part(key_part1, key_part2, key_part3) ) ENGINE=INNODB CHARSET=utf8;
CREATE TABLE s2 ( id INT AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), INDEX idx_key1 (key1), UNIQUE INDEX idx_key2 (key2), INDEX idx_key3 (key3), INDEX idx_key_part(key_part1, key_part2, key_part3) ) ENGINE=INNODB CHARSET=utf8;
# 创建函数 DELIMITER // CREATE FUNCTION rand_string1(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 ;
# 如果报错1418, This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) SET GLOBAL log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。
# 创建存储过程 DELIMITER // CREATE PROCEDURE insert_s1 (IN min_num INT (10),IN max_num INT (10)) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO s1 VALUES( (min_num + i), rand_string1(6), (min_num + 30 * i + 5), rand_string1(6), rand_string1(10), rand_string1(5), rand_string1(10), rand_string1(10)); UNTIL i = max_num END REPEAT; COMMIT; END // DELIMITER ;
DELIMITER // CREATE PROCEDURE insert_s2 (IN min_num INT (10),IN max_num INT (10)) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO s2 VALUES( (min_num + i), rand_string1(6), (min_num + 30 * i + 5), rand_string1(6), rand_string1(10), rand_string1(5), rand_string1(10), rand_string1(10)); UNTIL i = max_num END REPEAT; COMMIT; END // DELIMITER ;
## 有几个select, 就说明有几个id #2. id:在一个大的查询语句中每个SELECT关键字都对应一个唯一的id SELECT * FROM s1 WHERE key1 = 'a';
SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2);
SELECT * FROM s1 UNION SELECT * FROM s2;
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
EXPLAIN SELECT * FROM s1 INNER JOIN s2;
EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
## 优化器将子查询重写成了连接插叙, 提升效率, 所以id都是1 ######查询优化器可能对涉及子查询的查询语句进行重写,转变为多表查询的操作######## EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key2 FROM s2 WHERE common_field = 'a');
#Union去重 EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;
#`MySQL`选择使用临时表来完成`UNION`查询的去重工作,针对该临时表的查询的`select_type`就是 #`UNION RESULT` EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;
EXPLAIN SELECT * FROM s1 UNION ALL SELECT * FROM s2;
# 大的查询 包含一个 子查询, 大查询就是 `PRIMARY` #子查询: #如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是不相关子查询。 #该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`SUBQUERY` EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = 'a';
#如果包含子查询的查询语句不能够转为对应的`semi-join`的形式,并且该子查询是相关子查询, #则该子查询的第一个`SELECT`关键字代表的那个查询的`select_type`就是`DEPENDENT SUBQUERY` EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE s1.key2 = s2.key2) OR key3 = 'a'; #注意的是,select_type为`DEPENDENT SUBQUERY`的查询可能会被执行多次。
#在包含`UNION`或者`UNION ALL`的大查询中,如果各个小查询都依赖于外层查询的话,那除了 #最左边的那个小查询之外,其余的小查询的`select_type`的值就是`DEPENDENT UNION`。 EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2 WHERE key1 = 'a' UNION SELECT key1 FROM s1 WHERE key1 = 'b');
#对于包含`派生表`的查询,该派生表对应的子查询的`select_type`就是`DERIVED` EXPLAIN SELECT * FROM (SELECT key1, COUNT(*) AS c FROM s1 GROUP BY key1) AS derived_s1 WHERE c > 1;
#当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时, #该子查询对应的`select_type`属性就是`MATERIALIZED` EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2); #子查询被转为了物化表
# 4. partition(略):匹配的分区信息
# 5. type:针对单表的访问方法
#当表中`只有一条记录`并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory, #那么对该表的访问方法就是`system`。 CREATE TABLE t(i INT) ENGINE=MYISAM; INSERT INTO t VALUES(1);
EXPLAIN SELECT * FROM t;
#换成InnoDB CREATE TABLE tt(i INT) ENGINE=INNODB; INSERT INTO tt VALUES(1); EXPLAIN SELECT * FROM tt;
#当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是`const` EXPLAIN SELECT * FROM s1 WHERE id = 10005;
EXPLAIN SELECT * FROM s1 WHERE key2 = 10066;
#在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的 #(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则 #对该被驱动表的访问方法就是`eq_ref` EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
#当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是`ref` EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
#当对普通二级索引进行等值匹配查询,该索引列的值也可以是`NULL`值时,那么对该表的访问方法 #就可能是`ref_or_null` EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key1 IS NULL;
#单表访问方法时在某些场景下可以使用`Intersection`、`Union`、 #`Sort-Union`这三种索引合并的方式来执行查询 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
#`unique_subquery`是针对在一些包含`IN`子查询的查询语句中,如果查询优化器决定将`IN`子查询 #转换为`EXISTS`子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的`type` #列的值就是`unique_subquery` EXPLAIN SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 WHERE s1.key1 = s2.key1) OR key3 = 'a';
#如果使用索引获取某些`范围区间`的记录,那么就可能使用到`range`访问方法 EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
#同上 EXPLAIN SELECT * FROM s1 WHERE key1 > 'a' AND key1 < 'b';
## 覆盖索引(索引覆盖): 即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。 ## 这里使用的是联合索引, 是不需要通过主键回表查找的, 所以是一个覆盖索引 #当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是`index` EXPLAIN SELECT key_part2 FROM s1 WHERE key_part3 = 'a';
#最熟悉的全表扫描 EXPLAIN SELECT * FROM s1;
#6. possible_keys和key:可能用到的索引 和 实际上使用的索引
EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';
#7. key_len:实际使用到的索引长度(即:字节数) # 帮你检查`是否充分的利用上了索引`,`值越大越好`,主要针对于联合索引,有一定的参考意义。 EXPLAIN SELECT * FROM s1 WHERE id = 10005;
EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;
EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a';
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b' AND key_part3 = 'c';
EXPLAIN SELECT * FROM s1 WHERE key_part3 = 'a';
#练习: #varchar(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
#varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)
#char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)
#char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s2.key1 = UPPER(s1.key1);
# 9. rows:预估的需要读取的记录条数 # `值越小越好` EXPLAIN SELECT * FROM s1 WHERE key1 > 'z';
# 10. filtered: 某个表经过搜索条件过滤后剩余记录条数的百分比
#如果使用的是索引执行的单表扫描,那么计算时需要估计出满足除使用 #到对应索引的搜索条件外的其他搜索条件的记录有多少条。 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND common_field = 'a';
#对于单表查询来说,这个filtered列的值没什么意义,我们`更关注在连接查询 #中驱动表对应的执行计划记录的filtered值`,它决定了被驱动表要执行的次数(即:rows * filtered) EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE s1.common_field = 'a';
#11. Extra:一些额外的信息 #更准确的理解MySQL到底将如何执行给定的查询语句
#当查询语句的没有`FROM`子句时将会提示该额外信息 EXPLAIN SELECT 1;
#查询语句的`WHERE`子句永远为`FALSE`时将会提示该额外信息 EXPLAIN SELECT * FROM s1 WHERE 1 != 1;
#当我们使用全表扫描来执行对某个表的查询,并且该语句的`WHERE` #子句中有针对该表的搜索条件时,在`Extra`列中会提示上述额外信息。 EXPLAIN SELECT * FROM s1 WHERE common_field = 'a';
#当使用索引访问来执行对某个表的查询,并且该语句的`WHERE`子句中 #有除了该索引包含的列之外的其他搜索条件时,在`Extra`列中也会提示上述额外信息。 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' AND common_field = 'a';
#当查询列表处有`MIN`或者`MAX`聚合函数,但是并没有符合`WHERE`子句中 #的搜索条件的记录时,将会提示该额外信息 EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'abcdefg';
EXPLAIN SELECT MIN(key1) FROM s1 WHERE key1 = 'NlPros'; #NlPros 是 s1表中key1字段真实存在的数据
#select * from s1 limit 10;
#当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以 #使用覆盖索引的情况下,在`Extra`列将会提示该额外信息。比方说下边这个查询中只 #需要用到`idx_key1`而不需要回表操作: EXPLAIN SELECT key1,id FROM s1 WHERE key1 = 'a';
#有些搜索条件中虽然出现了索引列,但却不能使用到索引 #看课件理解索引条件下推 EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
#在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为 #其分配一块名叫`join buffer`的内存块来加快查询速度,也就是我们所讲的`基于块的嵌套循环算法` #见课件说明 EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
#当我们使用左(外)连接时,如果`WHERE`子句中包含要求被驱动表的某个列等于`NULL`值的搜索条件, #而且那个列又是不允许存储`NULL`值的,那么在该表的执行计划的Extra列就会提示`Not exists`额外信息 EXPLAIN SELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
#如果执行计划的`Extra`列出现了`Using intersect(...)`提示,说明准备使用`Intersect`索引 #合并的方式执行查询,括号中的`...`表示需要进行索引合并的索引名称; #如果出现了`Using union(...)`提示,说明准备使用`Union`索引合并的方式执行查询; #出现了`Using sort_union(...)`提示,说明准备使用`Sort-Union`索引合并的方式执行查询。 EXPLAIN SELECT * FROM s1 WHERE key1 = 'a' OR key3 = 'a';
#当我们的`LIMIT`子句的参数为`0`时,表示压根儿不打算从表中读出任何记录,将会提示该额外信息 EXPLAIN SELECT * FROM s1 LIMIT 0;
#有一些情况下对结果集中的记录进行排序是可以使用到索引的。 #比如: EXPLAIN SELECT * FROM s1 ORDER BY key1 LIMIT 10;
#同上。 EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field;
#执行计划中出现`Using temporary`并不是一个好的征兆,因为建立与维护临时表要付出很大成本的,所以 #我们`最好能使用索引来替代掉使用临时表`。比如:扫描指定的索引idx_key1即可 EXPLAIN SELECT key1, COUNT(*) AS amount FROM s1 GROUP BY key1;
#json格式的explain EXPLAIN FORMAT=JSON SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key2 WHERE s1.common_field = 'a';
EXPLAIN的进一步使用
EXPLAIN四种输出格式 传统格式 ,JSON格式 ,TREE格式 以及 可视化输出
传统格式
1 2 3 4 5 6 7 8 9
mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE -> s2.common_field IS NOT NULL; +----+-------------+-------+------------+------+---------------+----------+---------+--------------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+----------+---------+--------------------+------+----------+-------------+ | 1 | SIMPLE | s2 | NULL | ALL | idx_key1 | NULL | NULL | NULL | 9895 | 90.00 | Using where | | 1 | SIMPLE | s1 | NULL | ref | idx_key1 | idx_key1 | 303 | atguigudb1.s2.key1 | 1 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+----------+---------+--------------------+------+----------+-------------+ 2 rows inset, 1 warning (0.01 sec)
#1. 查询冗余索引 select * from sys.schema_redundant_indexes; #2. 查询未使用过的索引 select * from sys.schema_unused_indexes; #3. 查询索引的使用情况 select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted from sys.schema_index_statistics where table_schema='dbname' ;
表相关
1 2 3 4 5 6 7 8
# 1. 查询表的访问量 select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from sys.schema_table_statistics group by table_schema,table_name order by io desc; # 2. 查询占用bufferpool较多的表 select object_schema,object_name,allocated,data from sys.innodb_buffer_stats_by_table order by allocated limit 10; # 3. 查看表的全表扫描情况 select * from sys.statements_with_full_table_scans where db='dbname';
语句相关
1 2 3 4 5 6 7 8 9 10 11
#1. 监控SQL执行的频率 select db,exec_count,query from sys.statement_analysis order by exec_count desc; #2. 监控使用了排序的SQL select db,exec_count,first_seen,last_seen,query from sys.statements_with_sorting limit 1; #3. 监控使用了临时表或者磁盘临时表的SQL select db,exec_count,tmp_tables,tmp_disk_tables,query from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0 order by (tmp_tables+tmp_disk_tables) desc;
IO相关
1 2 3 4
#1. 查看消耗磁盘IO的文件 select file,avg_read,avg_write,avg_read+avg_write as avg_io from sys.io_global_by_file_by_bytes order by avg_read limit 10;
SET GLOBAL log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。
#随机产生字符串 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;
#创建往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表添加随机数据 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;
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");
# 验证数据mock完毕 SELECT COUNT(*) FROM class;
SELECT COUNT(*) FROM student;
# 2. 索引失效案例 # (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';
# 第一次查询 0.132s SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME='abcd';
# 添加单列索引 CREATE INDEX idx_age ON student(age);
# 第二次查询 0.045s SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME='abcd';
# 添加联合索引 CREATE INDEX idx_age_classid ON student(age, classId);
# 第三次查询 0.016s SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME='abcd';
# 添加联合索引 CREATE INDEX idx_age_classid_name ON student(age, classId, NAME);
# 第四次查询 0.015s SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME='abcd';
SHOW INDEX FROM student;
# (2) 最佳左前缀法则 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abcd';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.classid = 1 AND student.name = 'abcd';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE classid=4 AND student.age=30 AND student.name = 'abcd';
DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid ON student;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abcd';
# (3) 主键插入顺序
# (4) 计算、函数、类型转换(自动或手动)导致索引失效
# 创建一个关于 name 的索引 CREATE INDEX idx_name ON student(NAME);
SHOW INDEX FROM student;
# 此语句比下一条要好! (能够使用上索引) 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_sno ON student(stuno); SHOW INDEX FROM student;
# 没有使用上索引, 因为发生了计算 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;
# 使用了函数, 索引失效 EXPLAIN SELECT id, stuno, NAME FROM student WHERE SUBSTRING(NAME, 1,3)='abc';
# (5) 类型转化导致索引失效
# 未使用上索引 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME=123;
# name 是字符串类型, 发生了隐式转换, 调用了函数, 所以索引失效了 # 使用上索引了 EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME='123';
# (6) 范围条件右边的列索引失效 # 查看索引 SHOW INDEX FROM student;
# 情况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)));
#向图书表中添加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)));
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
# 添加索引 CREATE INDEX Y ON book(card);
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
CREATE INDEX X ON `type`(card);
## 左外连接中, 右边的表是不一定是被驱动表, 左边的表不一定是驱动表 EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
DROP INDEX Y ON book;
##### 左外连接情况, 在两个字段中, 只存在一个索引, 有索引的在上面 EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
# 情况2:内连接
DROP INDEX X ON `type`;
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;
#添加索引 CREATE INDEX Y ON book(card);
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;
CREATE INDEX X ON `type`(card);
##### 内连接情况, 在两个字段中, 每个都有一个索引, 由优化器决定谁在上面 ##### 此时 type 在上面, type 是驱动表, book 是被驱动表 # 结论:对于内连接来说,查询优化器可以决定谁作为驱动表,谁作为被驱动表出现的 EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;
##### 向 驱动表 添加数据, 这里的驱动表是 type , 所以给 type 添加数据 ##### 如果驱动表是 book , 那就给 book 添加数据 # 向分类表中添加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)));
# 结论: 对于内连接来说, 在两个表的连接条件都存在索引的情况下, # 会选择小表作为驱动表, "小表驱动大表" ##### 此时 TYPE 的数据更多, 有 40 条, type 是大表, type 是被驱动表 ##### book 是驱动表 EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;
SELECT COUNT(*) FROM book; SELECT COUNT(*) FROM TYPE;
INSERT INTO a VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(6,6);
INSERT INTO b VALUES(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
##### 查询优化器将这个左外连接转变成了内连接, 所以 a 变成了 被驱动表 ##### 写在 JOIN 左边的不一定是驱动表, 可能会被查询优化器优化 #测试1 EXPLAIN SELECT * FROM a LEFT JOIN b ON(a.f1=b.f1) WHERE (a.f2=b.f2);
#测试2 EXPLAIN SELECT * FROM a LEFT JOIN b ON(a.f1=b.f1) AND (a.f2=b.f2);
#测试3 EXPLAIN SELECT * FROM a JOIN b ON(a.f1=b.f1) WHERE (a.f2=b.f2);
#过程一: EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid;
EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid LIMIT 10;
#过程二:order by时不limit,索引失效 #创建索引 CREATE INDEX idx_age_classid_name ON student (age,classid,NAME);
#不限制,索引失效 EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid;
##### 索引覆盖, 不需要回表 # EXPLAIN SELECT SQL_NO_CACHE age,classid,name,id FROM student ORDER BY age,classid;
#增加limit过滤条件,使用上索引了。 EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid LIMIT 10;
#过程三:order by时顺序错误,索引失效
#创建索引age,classid,stuno CREATE INDEX idx_age_classid_stuno ON student (age,classid,stuno);
#以下哪些索引失效? EXPLAIN SELECT * FROM student ORDER BY classid LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY classid,NAME LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY age,classid,stuno LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY age,classid LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY age LIMIT 10;
#过程四:order by时规则不一致, 索引失效 (顺序错,不索引;方向反,不索引)
EXPLAIN SELECT * FROM student ORDER BY age DESC, classid ASC LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY classid DESC, NAME DESC LIMIT 10;
EXPLAIN SELECT * FROM student ORDER BY age ASC,classid DESC LIMIT 10;
##### 两个字段都是降序的, 都是反的, 使用上了 idx_age_classid_stuno 索引, Extra Backward index scan EXPLAIN SELECT * FROM student ORDER BY age DESC, classid DESC LIMIT 10;
#过程五:无过滤,不索引
EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid;
EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid,NAME;
EXPLAIN SELECT * FROM student WHERE classid=45 ORDER BY age;
##### 先进行 order by 匹配索引进行排序, 排序完成之后再过滤 classid = 45, ##### 最后分页取前 10 条记录 EXPLAIN SELECT * FROM student WHERE classid=45 ORDER BY age LIMIT 10;
##### 创建一个 cid 索引, 可以直接用上 CREATE INDEX idx_cid ON student(classid); EXPLAIN SELECT * FROM student WHERE classid=45 ORDER BY age;
##### 使用了联合索引, 但是只使用了第一个字段, zipcode EXPLAIN SELECT * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%' AND address LIKE '%北京市%';
##### 使用了联合索引, 三个字段全部都用上了, 索引下推 EXPLAIN SELECT * FROM people WHERE zipcode='000001' AND lastname LIKE '张%' AND firstname LIKE '三%';
SET optimizer_switch = 'index_condition_pushdown=on';
#创建存储过程,向people表中添加1000000条数据,测试ICP开启和关闭状态下的性能 DELIMITER // CREATE PROCEDURE insert_people( max_num INT ) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO people ( zipcode,firstname,lastname,address ) VALUES ('000001', '六', '赵', '天津市'); UNTIL i = max_num END REPEAT; COMMIT; END //
DELIMITER ;
CALL insert_people(1000000);
##### 查看 people 表中的数据量 SELECT COUNT(*) FROM people;
SET profiling = 1;
##### 此时是使用了索引下推 Using index condition mysql> SELECT * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%'; # 0.221s +----+---------+-----------+----------+-----------+ | id | zipcode | firstname | lastname | address | +----+---------+-----------+----------+-----------+ | 1 | 000001 | 三 | 张 | 北京市 | +----+---------+-----------+----------+-----------+ 1 ROW IN SET (0.22 sec)
##### 不使用索引下推 mysql> SELECT /*+ no_icp (people) */ * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%'; # 4.166s +----+---------+-----------+----------+-----------+ | id | zipcode | firstname | lastname | address | +----+---------+-----------+----------+-----------+ | 1 | 000001 | 三 | 张 | 北京市 | +----+---------+-----------+----------+-----------+ 1 ROW IN SET (4.92 sec)
mysql> SHOW PROFILES; +----------+------------+----------------------------------------------------------------------------------------------+ | Query_ID | Duration | QUERY | +----------+------------+----------------------------------------------------------------------------------------------+ | 1 | 0.00009650 | SELECT * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%' | | 2 | 0.00019850 | SELECT DATABASE() | | 3 | 0.21733375 | SELECT * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%' | | 4 | 4.92306350 | SELECT /*+ no_icp (people) */ * FROM people WHERE zipcode='000001' AND lastname LIKE '%张%' | +----------+------------+----------------------------------------------------------------------------------------------+ 4 ROWS IN SET, 1 warning (0.00 sec)
[root@iZf8z0puua6p4won7xep5rZ ~]# mysqlcheck -o atguigudb3 user1 -uroot -p Enter password: atguigudb3.user1 note : Table does not support optimize, doing recreate + analyze instead status : OK [root@iZf8z0puua6p4won7xep5rZ ~]#
#课程评论表 CREATE TABLE class_comment( comment_id INT PRIMARY KEY AUTO_INCREMENT, class_id INT, comment_text VARCHAR(35), comment_time DATETIME, stu_id INT );
###创建向学生表中添加数据的存储过程 DELIMITER //
CREATE PROCEDURE batch_insert_student(IN START INT(10), IN max_num INT(10)) BEGIN DECLARE i INT DEFAULT 0; DECLARE date_start DATETIME DEFAULT ('2017-01-01 00:00:00'); DECLARE date_temp DATETIME; SET date_temp = date_start; SET autocommit=0; REPEAT SET i=i+1; SET date_temp = DATE_ADD(date_temp, INTERVAL RAND()*60 SECOND); INSERT INTO student(stu_id, stu_name, create_time) VALUES((START+i), CONCAT('stu_',i), date_temp); UNTIL i = max_num END REPEAT; COMMIT; END //
CREATE PROCEDURE batch_insert_class_comments(IN START INT(10), IN max_num INT(10)) BEGIN DECLARE i INT DEFAULT 0; DECLARE date_start DATETIME DEFAULT ('2018-01-01 00:00:00'); DECLARE date_temp DATETIME; DECLARE comment_text VARCHAR(25); DECLARE stu_id INT; SET date_temp = date_start; SET autocommit=0; REPEAT SET i=i+1; SET date_temp = DATE_ADD(date_temp, INTERVAL RAND()*60 SECOND); SET comment_text = SUBSTR(MD5(RAND()),1, 20); SET stu_id = FLOOR(RAND()*1000000); INSERT INTO class_comment(comment_id, class_id, comment_text, comment_time, stu_id) VALUES((START+i), 10001, comment_text, date_temp, stu_id); UNTIL i = max_num END REPEAT; COMMIT; END //
###需求###### SELECT p.comment_text, p.comment_time, stu.stu_name FROM class_comment AS p LEFT JOIN student AS stu ON p.stu_id = stu.stu_id WHERE p.class_id = 10001 ORDER BY p.comment_id DESC LIMIT 10000;
#####进行反范式化的设计###### #表的复制 CREATE TABLE class_comment1 AS SELECT * FROM class_comment;
#添加主键,保证class_comment1 与class_comment的结构相同 ALTER TABLE class_comment1 ADD PRIMARY KEY (comment_id);
SHOW INDEX FROM class_comment1;
#向课程评论表中增加stu_name字段 ALTER TABLE class_comment1 ADD stu_name VARCHAR(25);
#给新添加的字段赋值 UPDATE class_comment1 c SET stu_name = ( SELECT stu_name FROM student s WHERE c.stu_id = s.stu_id );
#查询同样的需求 SELECT comment_text, comment_time, stu_name FROM class_comment1 WHERE class_id = 10001 ORDER BY comment_id DESC LIMIT 10000;
#08-数据库的其他优化策略 CREATE TABLE `user1` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(255) DEFAULT NULL, `age` INT DEFAULT NULL, `sex` VARCHAR(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
####### SET GLOBAL log_bin_trust_function_creators = 1;
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 ;
### DELIMITER // CREATE PROCEDURE insert_user( max_num INT ) BEGIN DECLARE i INT DEFAULT 0; SET autocommit = 0; REPEAT SET i = i + 1; INSERT INTO `user1` ( NAME,age,sex ) VALUES ("atguigu",rand_num(1,20),"male"); UNTIL i = max_num END REPEAT; COMMIT; END // DELIMITER;
mysql> select @@transaction_isolation; ERROR 2013 (HY000): Lost connection to MySQL server during query No connection. Trying to reconnect... Connection id: 8 Current database: *** NONE ***
mysql> select * from account for update nowait; ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.
mysql> select * from account for update skip locked; Empty set (0.00 sec)
mysql> select * from student whereid = 5 for update; Empty set (0.00 sec)
mysql> insert into student(id, name, class) values(6, 'jerry', '一班'); ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
[root@iZf8z0puua6p4won7xep5rZ data]# vim iZf8z0puua6p4won7xep5rZ.err [root@iZf8z0puua6p4won7xep5rZ data]# pwd /www/server/data
[root@iZf8z0puua6p4won7xep5rZ data]# ll | grep iZf8z0puua6p4won7xep5rZ -rw-r----- 1 mysql mysql 135308 Oct 7 10:33 iZf8z0puua6p4won7xep5rZ.err -rw-r----- 1 mysql mysql 354 Oct 7 15:00 iZf8z0puua6p4won7xep5rZ.log -rw-r----- 1 mysql mysql 835 Oct 7 14:45 iZf8z0puua6p4won7xep5rZ.log.old -rw-r----- 1 mysql mysql 6 Sep 17 15:09 iZf8z0puua6p4won7xep5rZ.pid
[root@atguigu01 log]# mysqladmin -uroot -p flush-logs Enter password: mysqladmin: refresh failed; error: 'Could not open file '/var/log/mysqld.log' for error logging.'