【可能是全网最好的】MySQL高级入门总结笔记(上)

视图

含义:
MySQL从5.0.1版本开始提供的功能,是一种虚拟存在的表,行和列的数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的,只保存了sql逻辑,不保存查询结果。

应用场景:
多个地方用到相同的查询结果。
该查询结果使用的sql语句比较复杂。

好处:
实现了sql语句的重用
简化了复杂的sql操作,不必知道其细节
保护数据,提高安全性


类型 关键字 是否实际占用物理空间 使用
视图 VIEW 仅保持了sql逻辑 增删改查,一般只能查
TABLE 保存了数据 增删改查

案例 查询姓张的学生名和专业名
不使用视图

SELECT stuName,majorName
FROM stuinfo s
INNER JOIN major m ON s.majorId=m.id
WHERE s.stuname LIKE '张%'; 

//使用视图

CREATE VIEW v1
AS
SELECT stuName,majorName
FROM stuinfo s
INNER JOIN major m ON s.majorId=m.id;

SELECT * FROM v1
WHERE stuName LIKE '张%';

1 创建视图

语法:

CREATE VIEW 视图名 AS 查询语句;

案例1 查询姓名中包含a字符的员工名,部门名和工种信息

CREATE VIEW v2 
AS
SELECT last_name,department_name,job_title
FROM employees e
JOIN departments d ON e.department_id=d.department_id
JOIN jobs j ON j.job_id=e.job_id;

SELECT * FROM v2
WHERE last_name LIKE '%a%';

案例2 查询各部门的平均工资级别

CREATE VIEW v3
AS
SELECT AVG(salary) asl,department_id 
FROM employees
GROUP BY department_id;

SELECT v3.*,g.`grade` FROM v3
JOIN sal_grade g 
ON v3.`asl` BETWEEN g.`min_salary` AND g.`max_salary`;

案例3 查询平均工资最低的部门信息

SELECT * FROM departments 
WHERE department_id=
(SELECT department_id FROM v3 ORDER BY asl LIMIT 1);

案例4 查询平均工资最低的部门名和工资

CREATE VIEW v4
AS
SELECT * FROM v3 ORDER BY asl LIMIT 1;

SELECT d.`department_name`,v4.`asl` FROM v4
JOIN departments d
ON d.`department_id`=v4.`department_id`;

案例5 创建视图emp_v1,要求查询电话号码以’011’开头的员工姓名和工资、邮箱

CREATE VIEW emp_v1
AS
SELECT last_name,salary,email FROM employees
WHERE phone_number LIKE '011%';

SELECT * FROM emp_v1;

案例6 创建视图emp_v2,要求查询部门的最高工资高于12000的部门信息

CREATE VIEW emp_v2
AS
SELECT department_id FROM employees
GROUP BY department_id 
HAVING MAX(salary)>12000;

SELECT d.* FROM departments d
JOIN emp_v2 ON d.`department_id` = emp_v2.`department_id`;

2 修改视图

方式一 存在则修改,不存在则创建

CREATE OR REPLACE VIEW 视图名 AS 查询语句;

方式二

ALTER VIEW 视图名 AS 查询语句;

3 删除视图

语法:

DROP VIEW 视图名1,视图名2...;

4 查看视图

查看视图结构
语法: desc 视图名;
例:desc v3;

查看视图创建过程
语法:SHOW CREATE VIEW 视图名;
例: SHOW CREATE VIEW v3;


5 视图的更新

特点:
视图的可更新性和视图中查询的定义有关,以下类型的视图不能更新:

  • 包含以下关键字: 分组函数,DISTINCT,GROUP BY,HAVING,UNION,UNION ALL

  • 常量视图,例:

    CREATE VIEW v AS SELECT 'abc' NAME;
    
  • SELECT 中包含子查询,例:

    CREATE VIEW v AS SELECT(SELECT 1) AS number;
    
  • JOIN,例:

    CREATE VIEW v AS 
    SELECT * FROM employees e JOIN departments d
    ON e.department_id=d.department_id;
    
  • FROM 一个不能更新的视图,例:

    CREATE VIEW v2 AS 
    SELECT * FROM v;
    
  • WHERE 子句的子查询引用了 FROM 子句中的表,例:

    CREATE VIEW v AS 
    SELECT * FROM employees
    WHERE employees_id IN (SELECT DISTINCT manager_id FROM employees);
    

环境准备

CREATE OR REPLACE VIEW v1
AS
SELECT last_name,email FROM employees;

1 插入数据

INSERT INTO v1 VALUES('宁缺','[email protected]');

2 修改数据

UPDATE v1 SET last_name='桑桑' WHERE last_name='宁缺';

3 删除数据

DELETE FROM v1 WHERE last_name='桑桑';

综合练习

1 创建Book表,字段如下:
bid 整形,要求主键
bname 字符型,要求设置唯一非空
price 浮点型,要求默认值10
btypeId 类型编号,要求引用bookType表的id字段
已知bookType表字段如下:id,name

CREATE TABLE Book(
	bid INT PRIMARY KEY AUTO_INCREMENT,
	bname VARCHAR(10) UNIQUE NOT NULL,
	price FLOAT DEFAULT 10,
	btypeId INT,
	CONSTRAINT fk FOREIGN KEY(btypeId) REFERENCES bookType(id)
);

2 开启事务,向表中插入一行数据并结束

SET autocommit=0;
INSERT INTO Book VALUES(1,'将夜',20,2);
COMMIT;

3 创建视图,实现查询价格大于100的书名和类型名

CREATE VIEW v AS
SELECT bname,btypeId FROM Book b 
JOIN bookType t ON b.btypeId=t.id
WHERE b.price>100;

4 修改视图,实现查询价格在90-120之间的书名和价格

CREATE OR REPLACE v AS
SELECT bname,price
FROM Book
WHERE price BETWEEN 90 AND 120;

5 删除刚才创建的视图

DROP VIEW v;

变量

系统变量

根据作用范围分为全局变量,会话变量
全局变量是由系统提供的,属于服务器范围,服务器重启后重新赋值
会话变量仅针对当前会话(连接)有效


语法:
1 查看所有的全局变量

SHOW GLOBAL VARIABLES;

2 查看所有的会话变量

SHOW SESSION VARIABLES;
SHOW VARIABLES;默认查看会话变量

3 查看部分系统变量
例: 查看字符集相关的系统变量

SHOW GLOBAL VARIABLES LIKE '%char%';
SHOW [SESSION] VARIABLES LIKE '%char%';

4 查看某个系统变量

SELECT @@global.系统变量名;
SELECT @@[session.]系统变量名;不写默认为session

例:查看自动提交

SELECT @@global.autocommit;
SELECT @@[session.]autocommit;

例:查看隔离级别

SELECT @@global.transaction_isolation;
SELECT @@[session.]transaction_isolation;

5 为某个系统变量赋值

SET GLOBAL|[SESSION] 系统变量名 =;
SET @@GLOBAL|[SESSION].系统变量名 =;

如果是全局级别需要加global,如果是会话级别加session或不写。


自定义变量

说明: 变量是用户自定义的,不是由系统提供
使用步骤: ①声明②赋值③使用
根据作用范围分为用户变量,局部变量


用户变量

作用域: 针对于当前会话(连接)有效,同于会话变量的作用域
应用在任何地方,可以放在 BEGIN END 里面或外面
①声明并初始化

SET @用户变量名=;
SET @用户变量名: =;
SELECT @用户变量名: =;

②赋值(更新用户变量值)
方式一:通过 SET 或 SELECT

SET @用户变量名=;
SET @用户变量名: =;
SELECT @用户变量名: =;

例:

SET @name='abc';
SET @name=1;

方式二:通过 SELECT INTO

SELECT 字段 INTO @用户变量名 FROM;

例:

SET @count=1;
SELECT COUNT(*) INTO @count FROM employees;
SELECT @count;//107

③查询

SELECT @变量名;

案例 声明2个用户变量并赋初值,求和并打印

SET @num1 = 1;
SET @num2 = 2;
SET @sum = @num1 + @num2;
SELECT @sum; //3

局部变量

作用域: 仅在定义它的 BEGIN END 中有效
应用在 BEGIN END 中的第一句话
使用:
①声明

DECLARE 变量名 类型;
DECLARE 变量名 类型 DEFAULT;

②赋值
方式一:通过 SET 或 SELECT

SET 局部变量名=;
SET 局部变量名: =;
SELECT @局部变量名: =;

方式二:通过 SELECT INTO

SELECT 字段 INTO 局部变量名 FROM;

③查询

SELECT 局部变量名;

类型 作用域 定义和使用的位置 语法
用户变量 当前会话 会话中的任何地方 必须加@符号,不用限定类型
局部变量 BEGIN END中 只能在 BEGIN END中,且为第一句 一般不加@符号,需要限定类型

存储过程

含义:
一组预先定义好的sql语句的集合

好处:
提高代码重用性、简化操作、减少编译次数,减少和数据库连接次数

1 创建

语法:

CREATE produce 存储过程名(参数列表)
BEGIN
  sql语句...
END

注意:

  • 1 参数列表包含三部分: 参数模式 参数名 参数类型
    例: IN NAME VARCHAR
    参数模式:
    IN 该参数可作为输入
    OUT 该参数可作为输出,即返回值
    INOUT 该参数既可以作为输入,也可以作为输出

  • 2 如果存储过程里面仅有一句sql语句,BEGIN END 可以省略

  • 3 存储过程体中的每条sql语句必须加;,存储过程的结尾可以用 DELIMITER 重新设置
    DELIMITER $ 可将 $作为结束标记


2 调用

语法:

CALL 存储过程名(实参列表);

环境准备,admin表:
在这里插入图片描述
1 空参列表
案例 插入到admin表中2条记录

DELIMITER $
CREATE PROCEDURE myp1()
BEGIN
   INSERT INTO admin(username,PASSWORD) VALUES('aaa','123'),
   ('bbb','123');
END$

注意,需要在命令行中执行
在这里插入图片描述
调用后,再次查询admin表,发现成功插入
在这里插入图片描述


2 带in模式参数的存储过程

案例1 根据女神名查询对应的男生信息

DELIMITER $
CREATE PROCEDURE myp2(IN girl_name VARCHAR(20))
BEGIN
   SELECT bo.* FROM boys bo 
   RIGHT JOIN beauty b ON bo.id=b.boyfriend_id
   WHERE b.name=girl_name;
END$

在这里插入图片描述


案例2 判断admin用户是否登陆成功

CREATE PROCEDURE myp3(IN usr VARCHAR(20),IN pwd VARCHAR(20))
BEGIN
   DECLARE result VARCHAR(20) DEFAULT '';
   SELECT COUNT(*) INTO result FROM admin
   WHERE username=usr AND PASSWORD=pwd;
   SELECT IF(result=0,'失败','成功');
END$

在这里插入图片描述


3 带out模式参数的存储过程

案例1 根据女神名返回对应男生名

CREATE PROCEDURE myp4(IN girlName VARCHAR(20),OUT boyName VARCHAR(20))
BEGIN
    SELECT bo.boyName INTO boyName 
    FROM boys bo
    JOIN beauty b ON bo.id=b.boyfriend_id
    WHERE b.name=girlName;
END$

在这里插入图片描述


案例2 根据女神名,返回对应男生名和其魅力值

CREATE PROCEDURE myp5(IN girlName VARCHAR(20),OUT boyName VARCHAR(20),OUT usercp INT)
BEGIN
    SELECT bo.boyName,bo.userCP INTO boyName,usercp 
    FROM boys bo
    JOIN beauty b ON bo.id=b.boyfriend_id
    WHERE b.name=girlName;
END$

在这里插入图片描述


4 带inout模式参数的存储过程

案例 传入a和b两个值,最终a和b翻倍并返回

CREATE PROCEDURE myp6(INOUT a INT,INOUT b INT)
BEGIN
    SET a=a*2;
    SET b=b*2;
END$

在这里插入图片描述
3 删除
语法:

DROP PROCEDURE 存储过程名;

4 查看存储过程的信息
语法:

SHOW CREATE PROCEDURE 存储过程名;

存储过程习题

1 创建存储过程实现传入用户名和密码,插入到admin表

CREATE PROCEDURE p(IN username VARCHAR(20),IN PASSWORD VARCHAR(20))
BEGIN
    INSERT INTO admin(username,PASSWORD) VALUES(username,PASSWORD);
END$

2 创建存储过程实现传入女神编号,返回女神名称和女神电话

CREATE PROCEDURE p(IN girl_id INT,OUT girl_name VARCHAR(20),OUT girl_tel VARCHAR(20))
BEGIN
    SELECT NAME,phone INTO girl_name,girl_tel
    FROM beauty WHERE id=girl_id;
END$

3 创建存储过程实现传入两个女神生日,返回大小

CREATE PROCEDURE p(IN date1,IN date2,OUT res)
BEGIN
    SELECT DATEDIFF(date1,date2)
    INTO res;
END$

4 创建存储过程实现传入一个日期,格式化为xx年xx月xx日并返回

CREATE PROCEDURE datep(IN DATE DATE,OUT result VARCHAR(20))
BEGIN
   SELECT DATE_FORMAT(DATE,'%y年%m月%d日')
   INTO result;
END$

在这里插入图片描述
5 创建存储过程,实现传入女神名,返回 女神名 AND 男神名 格式的字符串

CREATE PROCEDURE p(IN NAME VARCHAR(20),OUT str VARCHAR(20))
BEGIN
   SELECT CONCAT(b.name,' and ',bo.boyName)
   FROM beauty b JOIN boys bo
   ON b.boyfriend_id=bo.id
   WHERE b.name=NAME;
END$

在这里插入图片描述

6 创建存储过程,根据传入的条目数和起始索引,查询beauty表记录

CREATE PROCEDURE p(IN INDEX INT,IN num INT)
BEGIN
   SELECT * FROM beauty LIMIT INDEX,num;
END$

在这里插入图片描述


函数

含义和存储过程类似,区别是:
存储过程可以有0或多个返回值,适合做批量插入、批量更新
函数有且仅有一个返回值,适合做处理数据后返回一个结果


1 创建

语法:

CREATE FUNCTION 函数名(参数列表) RETURNS 返回类型
BEGIN
 函数体
END

注意:
参数列表包含 参数名,参数类型
函数体必须有 RETURN 语句
当函数体只有一句话时可以省略 BEGIN END
使用 DELIMITER 设置结束标记

2 调用

SELECT 函数名(参数列表);

案例1 无参数有返回值:返回公司的员工个数

CREATE FUNCTION f1() RETURNS INT
BEGIN
  DECLARE num INT DEFAULT 0;
  SELECT COUNT(*) INTO num FROM employees;
  RETURN num;
END$

如果出现1418错误,在客户端上执行SET GLOBAL log_bin_trust_function_creators = 1$
在这里插入图片描述


案例2 有参数有返回值:根据公司的员工名返回他的工资

CREATE FUNCTION f2(ename VARCHAR(20)) RETURNS DOUBLE
BEGIN
  DECLARE salary DOUBLE DEFAULT 0;
  SELECT employees.salary INTO salary FROM employees
  WHERE last_name = ename;
  RETURN salary;
END$

在这里插入图片描述


案例3 根据部门名返回平均工资

CREATE FUNCTION f3(dname VARCHAR(20)) RETURNS DOUBLE
BEGIN
  DECLARE sal DOUBLE DEFAULT 0;
  SELECT AVG(salary) INTO sal FROM employees e
  JOIN departments d ON e.department_id=d.department_id
  WHERE d.department_name=dname;
  RETURN sal;
END$

在这里插入图片描述
案例4 传入两个float值,返回和

CREATE FUNCTION fadd(num1 FLOAT,num2 FLOAT) RETURNS FLOAT
BEGIN
   SET @sum=num1+num2;
   RETURN @sum;
END$

在这里插入图片描述


3 查看

语法:

show create function 函数名;

4 删除

语法:

drop function 函数名;

流程控制结构

顺序结构:程序从上往下依次执行
分支结构:程序从多条路径中选择一条执行
循环结构:程序在满足一定条件的基础上,重复执行一段代码


分支结构

1 IF 函数
功能: 实现简单的双分支
语法:

SELECT IF(exp1,exp2,exp3);

如果exp1成立返回exp2的值,否则返回exp3的值

应用: 任何地方


2 CASE 结构
作为表达式:
情况1:类似switch ,实现等值判断
语法:

CASE 变量名|表达式|字段
WHEN1 THEN 返回值1
...
WHEN 值n THEN 返回值n
ELSE 要返回的值m
END;

情况2:类似多重if,实现区间判断
语法:

CASE 
WHEN 条件1 THEN 返回值1
...
WHEN 条件n THEN 返回值n
ELSE 要返回的值m
END;

作为独立语句时:
情况1:类似switch ,实现等值判断

CASE 变量名|表达式|字段
WHEN1 THEN 语句1;
...
WHEN 值n THEN 语句值n;
ELSE 语句m;
END CASE;

情况2:类似多重if,实现区间判断
语法:

CASE 
WHEN 条件1 THEN 语句1;
...
WHEN 条件n THEN 语句n;
ELSE 语句m;
END CASE;

特点:

  • 可以作为表达式,嵌套在其他语句使用,可以放在任何地方
  • 可以作为独立语句使用,只能放在 BEGIN END 中,END 后要加 CASE
  • 如果when中的值或条件成立,执行对应then后面的语句,结束 CASE,都不满足执行 ELSE 中的语句
  • ELSE 可以省略,如果都不满足返回 NULL

案例 创建存储过程,根据传入的成绩,返回等级

CREATE PROCEDURE p(IN grade INT)
BEGIN
   CASE 
   WHEN grade BETWEEN 90 AND 100 THEN SELECT 'A';
   WHEN grade BETWEEN 80 AND 90 THEN SELECT 'B';
   WHEN grade BETWEEN 60 AND 80 THEN SELECT 'C';
   ELSE SELECT 'D';
   END CASE;
END$

在这里插入图片描述


3 IF 结构
功能: 实现多重分支
限制: 只能用在 BEGIN END 中
语法:

IF 条件1 THEN 语句1;
ELSEIF 条件2 THEN 语句2;
...
ELSE 语句n;
END IF;

案例 根据传入的成绩,返回等级

CREATE FUNCTION f(grade INT) RETURNS CHAR
BEGIN
   IF grade BETWEEN 90 AND 100 THEN RETURN 'A';
   ELSEIF grade BETWEEN 80 AND 90 THEN RETURN 'B';
   ELSEIF grade BETWEEN 60 AND 80 THEN RETURN 'C';
   ELSE RETURN 'D';
   END IF;
END$

在这里插入图片描述


循环结构

分类:
WHILE LOOP REPEAT
循环控制:
ITERATE 类似于continue 结束本次循环,继续下一次
LEAVE 类似于break,结束当前所在循环

1 WHILE
语法:

[标签名:]WHILE 循环条件 DO
循环体;
END WHILE [标签名];

2 LOOP
语法:

[标签名:] LOOP
 循环体;
END LOOP [标签名];

可以模拟死循环

3 REPEAT
语法:

[标签名:] REPEAT
循环体;
UNTIL 结束循环的条件
END REPEAT [标签名];

案例 批量插入,根据插入次数插入admin表中多条记录

DELIMITER $
CREATE PROCEDURE p(IN insert_time INT)
BEGIN
  DECLARE i INT DEFAULT 1;
  WHILE i<=insert_time DO 
  INSERT INTO admin(username,PASSWORD) VALUES ('a','1');
  SET i=i+1;
  END WHILE;
END$

案例 批量插入,根据插入次数插入admin表中多条记录,若次数大于5则停止

DELIMITER $
CREATE PROCEDURE p(IN insert_time INT)
BEGIN
  DECLARE i INT DEFAULT 1;
  a:WHILE i<=insert_time DO
  IF i>20 THEN LEAVE a;
  END IF;
  INSERT INTO admin(username,PASSWORD) VALUES ('a','1');
  SET i=i+1;
  END WHILE;
END$

案例 批量插入,根据插入次数插入admin表中多条记录,只插入偶数次

DELIMITER $
CREATE PROCEDURE p(IN insert_time INT)
BEGIN
  DECLARE i INT DEFAULT 0;
  a:WHILE i<=insert_time DO
  SET i=i+1;
  IF MOD(i,2)<>0 THEN ITERATE a;
  END IF;
  INSERT INTO admin(username,PASSWORD) VALUES ('a','1');
  END WHILE;
END$

Linux下MySQL的安装

这里使用的环境是centos8.0,vm15,linux的安装可以看我之前的文章
打开终端,键入命令:sudo dnf install @mysql将自动下载mysql8.0.17,一路按y即可。
在这里插入图片描述
安装完成后,启动MySQL并使它以后自动启动:$ sudo systemctl enable --now mysqld
(这里如果失败的话可能是安装时选择了自动分区…我换成自定义分区就好了)
要检查MySQL服务器是否正在运行,请输入:$ sudo systemctl status mysqld
在这里插入图片描述
运行mysql_secure_installation脚本,该脚本执行安全性相关的操作并设置root用户密码:
$ sudo mysql_secure_installation
一路按y,并设置密码即可。
之后输入mysql -uroot -p和你的密码即可成功登陆mysql。
在这里插入图片描述


索引

概述

官方定义: 索引是帮助MySQL高效获取数据的数据结构(有序)。
除了数据之外,数据库还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

有无索引对比示意图:
在这里插入图片描述
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(逻辑上相邻的记录在磁盘上并不一定物理相邻)。
此时如果我们想要查找Col2这一列值为3的这条记录,需要从第一条遍历到最后一条,一共需要7次,当数据增多时效率会更低。

为了加快Col2的查找,可以维护一个右边所示的二叉查找树(左孩子小于父结点,右孩子大于父结点),每个结点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找快速获取到相应数据。
此时如果我们要查找Col2这一列值为3的记录,只需要查找3次即可(3小于34,往左子树寻找->3小于5,往左子树寻找->成功)。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上,索引是数据库中用来提高性能的最常用的工具。


优缺点

优点

  • 类似于书籍的目录索引,提高数据检索的效率,降低数据库的IO成本。
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。

缺点

  • 实际上索引也是一张表,该表中保存了主键与索引字段,并指向实体类的记录,所以索引列也是要占用空间的。
  • 虽然索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

结构

索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的。
MySQL目前提供了以下4种索引:

  • BTree 索引 : 最常见的索引类型,大部分索引都支持 BTree索引。
  • Hash 索引:只有Memory引擎支持 , 使用场景简单 。
  • R-tree 索引(空间索引):空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少。
  • Full-text (全文索引) :全文索引也是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从Mysql5.6版本开始支持全文索引。
索引 InnoDB引擎 MyISAM引擎 Memory引擎
BTREE索引 支持 支持 支持
HASH 索引 不支持 不支持 支持
R-tree 索引 不支持 支持 不支持
Full-text 5.6版本之后支持 支持 不支持
表 MyISAM、InnoDB、Memory三种存储引擎对各种索引类型的支持

我们平常所说的索引,如果没有特别指明,都是指B+树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引、复合索引、前缀索引、唯一索引默认都是使用 B+tree 索引,统称为索引。


BTree
BTree又叫多路平衡搜索树,一颗m叉的BTree特性如下:

  • 树中每个结点最多包含m个孩子。
  • 除根结点与叶子结点外,每个结点至少有[ceil(m/2)]个孩子。
  • 若根结点不是叶子结点,则至少有两个孩子。
  • 所有的叶子结点都在同一层。
  • 每个非叶子结点由n个key与n+1个指针组成,其中[ceil(m/2)-1] <= n <= m-1(ceil为向上取整)。当n>m-1时,中间结点分裂到父节点,两边结点分裂。

以5叉BTree为例,m=5,所以 [ceil(5/2)-1]=2 <= n <=4 。
插入 C N G A H E K Q M F W L T Z D P R X Y S 数据为例。
演变过程如下:
1) 插入前4个字母 C N G A
由于字母顺序A<C<G<N,所以插入结果为ACGN:
在这里插入图片描述
2) 插入H,此时由于n=4+1>4,中间元素G字母向上分裂成为父结点

在这里插入图片描述

3) 插入E,K,Q不需要分裂
在这里插入图片描述
4) 插入M,此时图中右下角结点HKMNQ的k=5,因此中间元素M字母向上分裂到父节点G
在这里插入图片描述
5) 插入F,W,L,T不需要分裂

在这里插入图片描述
6) 插入Z,此时NQTWZ的k=5,中间元素T需要向上分裂到父结点GM中
在这里插入图片描述
7)插入D,ACDEF的k=5,中间元素D向上分裂到父结点GMT中。然后插入P,R,X,Y不需要分裂
在这里插入图片描述

8) 最后插入S,NPQRS结点n=5,中间结点Q向上分裂到父结点DGMT中,但分裂后父节点DGMQT的n=5,中间节点M向上分裂
在这里插入图片描述
到此该BTREE树就已经构建完成了,对于前面Btree的特点,该数的m=5,因此:

  • 树中每个结点最多包含5个孩子。
  • 除根结点与叶子结点外,每个结点至少有3个孩子。
  • 若根结点不是叶子结点,则至少有两个孩子。
  • 所有的叶子结点都在同一层。
  • 每个非叶子结点由n个key与n+1个指针组成,其中2<= n <=4(ceil为向上取整)。当n>4时,中间结点分裂到父节点,两边结点分裂。

BTREE和二叉树相比, 查询数据的效率更高, 因为对于相同的数据量来说,BTREE的层级结构比二叉树小(深度小,需要遍历的次数小),因此搜索速度快。


B+Tree
B+Tree为BTree的变种,B+Tree与BTree的区别为:

  • n叉B+Tree最多含有n个key,而BTree最多含有n-1个key。(之前的5叉树每个结点最多有4个key,如果是B+数则最多有5个,如下图的3叉B+树,每个结点最多3个key)
  • B+Tree的叶子结点保存所有的key信息,依key大小顺序排列。
  • 所有的非叶子结点都可以看作是key的索引部分。
  • 由于B+Tree只有叶子结点保存key信息,查询任何key都要从root走到叶子。所以B+Tree的查询效率更加稳定。

在这里插入图片描述


MySQL中的B+Tree
MySql索引数据结构对B+Tree进行了优化,在B+Tree的基础上,增加一个指向相邻叶子结点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能。

在这里插入图片描述

MySQL中的 B+Tree 索引结构示意图

分类

1) 单值索引 :即一个索引只包含单个列,一个表可以有多个单列索引。
2) 唯一索引 :索引列的值必须唯一,但允许有空值。
3) 复合索引 :即一个索引包含多个列,例如用身份证号和考生号两列作为复合索引。


语法及实例

环境准备
在Linux终端键入以下sql代码

CREATE DATABASE test2 DEFAULT CHARSET=utf8mb4;

USE test2;

CREATE TABLE `city` (
  `city_id` INT(11) NOT NULL AUTO_INCREMENT,
  `city_name` VARCHAR(50) NOT NULL,
  `country_id` INT(11) NOT NULL,
  PRIMARY KEY (`city_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE TABLE `country` (
  `country_id` INT(11) NOT NULL AUTO_INCREMENT,
  `country_name` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`country_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;


INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(1,'西安',1);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(2,'NewYork',2);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(3,'北京',1);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(4,'上海',1);

INSERT INTO `country` (`country_id`, `country_name`) VALUES(1,'China');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(2,'America');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(3,'Japan');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(4,'UK');

此时环境准备完毕,创建了city表和country表:
在这里插入图片描述


创建索引
语法:

CREATE 	[UNIQUE|FULLTEXT|SPATIAL]  INDEX 索引名 
[USING  索引类型]#如果不指定则为B+Tree
ON 表名(列名,...)

例:给city表的city_name列创建索引

CREATE INDEX index_city_cityname ON city(city_name);

在这里插入图片描述


查看索引
语法:

SHOW INDEX FROM 表名;

例:查看city表的索引

SHOW INDEX FROM city;

结果(主键默认有主键索引),可以看到默认索引类型为B+tree。
在这里插入图片描述


删除索引
语法:

DROP INDEX 索引名 ON 表名

例:删除刚才在city表创建的索引

DROP INDEX index_city_citynam ON city;

在这里插入图片描述


Alter命令
1)添加一个主键,这意味着索引值必须是唯一的,且不能为NULL

ALTER  TABLE  表名 ADD  PRIMARY  KEY(列名);

例:给city表的id添加主键索引

ALTER TABLE city ADD PRIMARY KEY(id);

2)这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)

ALTER  TABLE  表名  ADD  UNIQUE 索引名(列名);	

例:给city表的city_name添加唯一索引

ALTER TABLE city ADD UNIQUE idx_city_name(city_name);

3)添加普通索引, 索引值可以出现多次。

 ALTER  TABLE  表名  ADD  INDEX 索引名(列名);	

例:给city表的city_name添加普通索引

ALTER TABLE city ADD INDEX idx_city_name(city_name);

4)该语句指定了索引为FULLTEXT, 用于全文索引

ALTER  TABLE  表名  ADD  FULLTEXT  索引名(列名);

设计原则

  • 查询频次较高,且数据量比较大的表建立索引。
  • 索引字段的选择,最佳候选列应当从where子句的条件中提取,如果where子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。
  • 使用唯一索引,区分度越高,使用索引的效率越高。
  • 索引可以有效的提升查询数据的效率,但索引数量不是多多益善,索引越多,维护索引的代价自然也就越高。对于插入、更新、删除等DML操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低DML操作的效率,增加相应操作的时间消耗。
  • 使用短索引,索引创建之后也是使用硬盘来存储的,因此提升索引访问的I/O效率,也可以提升总体的访问效率。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的可以有效的提升MySQL访问索引的I/O效率。
  • 利用最左前缀,N个列组合而成的组合索引,那么相当于是创建了N个索引,如果查询时where子句中使用了组成该索引的前几个字段,那么这条查询SQL可以利用组合索引来提升查询效率。

创建复合索引:

CREATE INDEX idx_name_email_status ON tb_seller(NAME,email,STATUS);

就相当于

对name 创建索引 ;
对name , email 创建了索引 ;
对name , email, status 创建了索引 ;

游标/光标

游标是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行循环的处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。

声明光标:

DECLARE 游标名 CURSOR FOR 查询语句 ;

OPEN 光标:

OPEN 游标名 ;

FETCH 光标:

FETCH 游标名 INTO 变量名1 [, 变量名2] ...

CLOSE 光标:

CLOSE 游标名 ;

环境准备

CREATE TABLE emp(
  id INT(11) NOT NULL AUTO_INCREMENT ,
  NAME VARCHAR(50) NOT NULL COMMENT '姓名',
  age INT(11) COMMENT '年龄',
  salary INT(11) COMMENT '薪水',
  PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8 ;

INSERT INTO emp(id,NAME,age,salary) VALUES
(NULL,'范闲',55,3800),
(NULL,'宁缺',60,4000),
(NULL,'司理理',38,2800),
(NULL,'桑桑',42,1800);

案例 查询emp表中数据,并获取进行展示

DELIMITER $
CREATE PROCEDURE p()
BEGIN
 DECLARE eid INT;
 DECLARE ename VARCHAR(20);
 DECLARE eage INT;
 DECLARE esalary INT;
 DECLARE resultSet CURSOR FOR SELECT * FROM emp;

 OPEN resultSet;
 
 FETCH resultSet INTO eid,ename,eage,esalary;
 SELECT CONCAT('id=',eid , ', name=',ename, ', age=', eage, ', 薪资为: ',esalary);

 CLOSE resultSet;
END$

在这里插入图片描述


案例 查询emp表中数据,并循环获取进行展示

DELIMITER $
CREATE PROCEDURE p2()
BEGIN
 DECLARE eid INT;
 DECLARE ename VARCHAR(20);
 DECLARE eage INT;
 DECLARE esalary INT;
 DECLARE has_data BOOL DEFAULT TRUE;#默认有数据
 
 DECLARE resultSet CURSOR FOR SELECT * FROM emp;
 DECLARE EXIT HANDLER FOR NOT FOUND SET has_data=FALSE;#查不到时设置boolean变量为false

 OPEN resultSet;
 
 REPEAT
    FETCH resultSet INTO eid,ename,eage,esalary;
    SELECT CONCAT('id=',eid , ', name=',ename, ', age=', eage, ', 薪资为: ',esalary);
    UNTIL has_data=FALSE
 END REPEAT;

 CLOSE resultSet;
END$

在这里插入图片描述


触发器

介绍

触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。

使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

触发器类型 NEW 和 OLD的使用
INSERT 型触发器 NEW 表示将要或者已经新增的数据
UPDATE 型触发器 OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据
DELETE 型触发器 OLD 表示将要或者已经删除的数据

创建

语法

CREATE TRIGGER 触发器名 
BEFORE/AFTER INSERT/UPDATE/DELETE
ON 表名 FOR EACH ROW 

BEGIN
 触发语句 ;
END;

案例 通过触发器记录 emp 表的数据变更日志 , 包含增加, 修改 , 删除 ;
首先创建一张日志表 :

CREATE TABLE emp_logs(
  id INT PRIMARY KEY AUTO_INCREMENT,
  operation VARCHAR(20) COMMENT '操作类型, insert/update/delete',
  operate_time DATETIME  COMMENT '操作时间',
  operate_id INT COMMENT '操作表的ID',
  operate_params VARCHAR(500) COMMENT '操作参数'
);

插入操作触发器

CREATE TRIGGER emp_ins
AFTER INSERT
ON emp FOR EACH ROW
BEGIN
  INSERT INTO emp_logs VALUES 
  (NULL,'insert',NOW(),new.id,CONCAT('插入后(id:',new.id,', name:',new.name,', age:',new.age,', salary:',new.salary,')'));
END$

插入一条数据后,查看日志表:
在这里插入图片描述

更新操作触发器

CREATE TRIGGER emp_update
AFTER UPDATE
ON emp FOR EACH ROW
BEGIN
  INSERT INTO emp_logs VALUES 
  (NULL,'update',NOW(),new.id,
  CONCAT('修改前(id:',old.id,', name:',old.name,', age:',old.age,', salary:',old.salary,') , 
  修改后(id',new.id, 'name:',new.name,', age:',new.age,', salary:',new.salary,')')); 
END$

更新一条数据后,查看日志表:
在这里插入图片描述

删除操作触发器

CREATE TRIGGER emp_del
AFTER DELETE
ON emp FOR EACH ROW
BEGIN
  INSERT INTO emp_logs VALUES 
  (NULL,'delete',NOW(),old.id,
  CONCAT('删除前(id:',old.id,', name:',old.name,', age:',old.age,', salary:',old.salary,')'));  
END$

删除一条数据后,查看日志表:
在这里插入图片描述


删除

语法结构 :

drop trigger [schema_name.]trigger_name;

如果没有指定 schema_name,默认为当前数据库 。


查看

可以通过执行 SHOW TRIGGERS 命令查看触发器的状态、语法等信息。
语法结构 :

show triggers;

存储引擎

存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式 。
存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型。
可以通过show engines ;, 来查询当前数据库支持的存储引擎 :
在这里插入图片描述


几种常用的存储引擎

特点 InnoDB MyISAM MEMORY MERGE NDB
存储限制 64TB 没有
事务安全 支持
锁机制 行锁(适合高并发) 表锁 表锁 表锁 行锁
B树索引 支持 支持 支持 支持 支持
哈希索引 支持
全文索引 支持(5.6版本之后) 支持
集群索引 支持
数据索引 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据可压缩 支持
空间使用 N/A
内存使用 中等
批量插入速度
支持外键 支持

存储引擎的选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。

  • InnoDB : 是Mysql的默认存储引擎,用于事务处理应用程序,支持外键。
  • 如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。InnoDB存储引擎除了有效的降低由于删除和更新导致的锁定, 还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB是最合适的选择。
  • MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
  • MEMORY:将所有数据保存在RAM中,在需要快速定位记录和其他类似数据环境下,可以提供几块的访问。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY表通常用于更新不太频繁的小表,用以快速得到访问结果。
  • MERGE:用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象引用他们。MERGE表的优点在于可以突破对单个MyISAM表的大小限制,并且通过将不同的表分布在多个磁盘上,可以有效的改善MERGE表的访问效率。这对于存储诸如数据仓储等VLDB环境十分合适。

SQL优化

查看SQL执行频率

通过show [session|global] status 命令可以提供服务器状态信息。
show [session|global] status可以根据需要加上参数“session”或者“global”来显示 session 级(当前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果。如果不写,默认使用参数是“session”。

例:显示当前 session 中所有统计参数的值

SHOW STATUS LIKE 'Com_______';

在这里插入图片描述


例:显示innoDB引擎的global统计结果

SHOW GLOBAL STATUS LIKE 'Innodb_rows_%';

在这里插入图片描述


Com_xxx 表示每个 xxx 语句执行的次数,通常比较常用的是以下几个统计参数。

参数 含义
Com_select 执行 select 操作的次数,一次查询只累加 1。
Com_insert 执行 INSERT 操作的次数,对于批量插入的 INSERT 操作,只累加一次。
Com_update 执行 UPDATE 操作的次数。
Com_delete 执行 DELETE 操作的次数。
Innodb_rows_read select 查询返回的行数。
Innodb_rows_inserted 执行 INSERT 操作插入的行数。
Innodb_rows_updated 执行 UPDATE 操作更新的行数。
Innodb_rows_deleted 执行 DELETE 操作删除的行数。
Connections 试图连接 MySQL 服务器的次数。
Uptime 服务器工作时间。
Slow_queries 慢查询的次数。

定位低效率执行SQL

可以通过以下两种方式定位执行效率较低的 SQL 语句。

  • 慢查询日志 : 通过慢查询日志定位那些执行效率较低的 SQL 语句,用–log-slow-queries[=file_name]选项启动时,mysqld 写一个包含所有执行时间超过 long_query_time 秒的 SQL 语句的日志文件。
  • show processlist : 慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化。

在这里插入图片描述

1) id列,用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
2) user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
3) host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
4) db列,显示这个进程目前连接的是哪个数据库
5) command列,显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
6) time列,显示这个状态持续的时间,单位是秒
7) state列,显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
8) info列,显示这个sql语句,是判断问题语句的一个重要依据


explain分析执行计划

通过以上步骤查询到效率低的 SQL 语句后,可以通过 EXPLAIN或者 DESC命令获取 MySQL如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

查询SQL语句的执行计划 : EXPLAIN SELECT * FROM employees;
在这里插入图片描述

字段 含义
id select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。
select_type 表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等
table 输出结果集的表
type 表示表的连接类型,性能由好到差的连接类型为( system —> const -----> eq_ref ------> ref -------> ref_or_null----> index_merge —> index_subquery -----> range -----> index ------> all )
possible_keys 表示查询时,可能使用的索引
key 表示实际使用的索引
key_len 索引字段的长度
rows 扫描行的数量
extra 执行情况的说明和描述

环境准备
在这里插入图片描述

CREATE TABLE `t_role` (
  `id` varchar(32) NOT NULL,
  `role_name` varchar(255) DEFAULT NULL,
  `role_code` varchar(255) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_role_name` (`role_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `t_user` (
  `id` varchar(32) NOT NULL,
  `username` varchar(45) NOT NULL,
  `password` varchar(96) NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user_role` (
  `id` int(11) NOT NULL auto_increment ,
  `user_id` varchar(32) DEFAULT NULL,
  `role_id` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_ur_user_id` (`user_id`),
  KEY `fk_ur_role_id` (`role_id`),
  CONSTRAINT `fk_ur_role_id` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `fk_ur_user_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;




insert into `t_user` (`id`, `username`, `password`, `name`) values('1','super','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','超级管理员');
insert into `t_user` (`id`, `username`, `password`, `name`) values('2','admin','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','系统管理员');
insert into `t_user` (`id`, `username`, `password`, `name`) values('3','itcast','$2a$10$8qmaHgUFUAmPR5pOuWhYWOr291WJYjHelUlYn07k5ELF8ZCrW0Cui','test02');
insert into `t_user` (`id`, `username`, `password`, `name`) values('4','stu1','$2a$10$pLtt2KDAFpwTWLjNsmTEi.oU1yOZyIn9XkziK/y/spH5rftCpUMZa','学生1');
insert into `t_user` (`id`, `username`, `password`, `name`) values('5','stu2','$2a$10$nxPKkYSez7uz2YQYUnwhR.z57km3yqKn3Hr/p1FR6ZKgc18u.Tvqm','学生2');
insert into `t_user` (`id`, `username`, `password`, `name`) values('6','t1','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','老师1');



INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('5','学生','student','学生');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('7','老师','teacher','老师');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('8','教学管理员','teachmanager','教学管理员');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('9','管理员','admin','管理员');
INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('10','超级管理员','super','超级管理员');


INSERT INTO user_role(id,user_id,role_id) VALUES(NULL, '1', '5'),(NULL, '1', '7'),(NULL, '2', '8'),(NULL, '3', '9'),(NULL, '4', '8'),(NULL, '5', '10') ;

explain 之 id
id 字段是 select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。id 情况有三种 :
1) id 相同表示加载表的顺序是从上到下。

EXPLAIN SELECT * FROM t_role,t_user,user_role
WHERE user_role.`role_id`=t_role.`id` AND user_role.`user_id`=t_user.`id`;

在这里插入图片描述
2) id 不同id值越大,优先级越高,越先被执行。

EXPLAIN SELECT * FROM t_role 
WHERE id = 
(SELECT role_id FROM user_role 
WHERE user_id = 
(SELECT id FROM t_user
 WHERE username = 'stu1'));

在这里插入图片描述
3) id 有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行。

EXPLAIN SELECT * FROM t_role r , (SELECT * FROM user_role ur WHERE ur.`user_id` = '2') a WHERE r.id = a.role_id ; 

在这里插入图片描述


explain 之 select_type

表示查询的类型,如下表,从上到下效率越来越低

select_type 含义
SIMPLE 简单的select查询,查询中不包含子查询或者UNION
PRIMARY 查询中若包含任何复杂的子查询,最外层查询标记为该标识
SUBQUERY 在SELECT 或 WHERE 列表中包含了子查询
DERIVED 在FROM 列表中包含的子查询,被标记为 DERIVED(衍生) MYSQL会递归执行这些子查询,把结果放在临时表中
UNION 若第二个SELECT出现在UNION之后,则标记为UNION ; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为 : DERIVED
UNION RESULT 从UNION表获取结果的SELECT

explain 之 table
展示这一行的数据是关于哪一张表的


explain 之 type

type 显示的是访问类型,是较为重要的一个指标,如下表,从上到下效率越来越低,一般要求达到range或ref即可。

type 含义
NULL MySQL不访问任何表,索引,直接返回结果
system 表只有一行记录(等于系统表),这是const类型的特例,一般不会出现
const 表示通过索引一次就找到了,const 用于比较primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL 就能将该查询转换为一个常亮。const于将 “主键” 或 “唯一” 索引的所有部分与常量值进行比较
eq_ref 类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描
ref 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个)
range 只检索给定返回的行,使用一个索引来选择行。 where 之后出现 between , < , > , in 等操作。
index index 与 ALL的区别为 index 类型只是遍历了索引树, 通常比ALL 快, ALL 是遍历数据文件。
all 将遍历全表以找到匹配的行

explain 之 key

  • possible_keys : 显示可能应用在这张表的索引, 一个或多个。
  • key : 实际使用的索引, 如果为NULL, 则没有使用索引。
  • key_len: 表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好 。

explain 之 rows
扫描行的数量。


explain 之 extra
其他的额外的执行计划信息,在该列展示 。

extra 含义
using filesort 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取, 称为 “文件排序”, 效率低。
using temporary 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于 order by 和 group by; 效率低
using index 表示相应的select操作使用了覆盖索引, 避免访问表的数据行, 效率不错。

show profile分析SQL

Mysql从5.0.37版本开始增加了对 show profilesshow profile 语句的支持。show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。
通过 have_profiling 参数,能够看到当前MySQL是否支持profile
在这里插入图片描述
查看profiling是否打开:
在这里插入图片描述
通过profiles,我们能够更清楚地了解SQL执行的过程。
在这里插入图片描述
通过show profile for query query_id查看具体某一条sql语句的执行各个阶段耗时:
在这里插入图片描述


trace分析优化器执行计划

MySQL5.6提供了对SQL的跟踪trace, 通过trace文件能够进一步了解为什么优化器选择A计划, 而不是选择B计划。
打开trace , 设置格式为 JSON,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

SET optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;

执行SQL语句 :

SELECT * FROM employees WHERE employee_id < 110;

最后, 检查information_schema.optimizer_trace就可以知道MySQL是如何执行SQL的 :

select * from information_schema.optimizer_trace;

索引的使用

验证索引对查询效率的提升

利用存储过程给emp表插入约五万条记录

delimiter $
CREATE PROCEDURE p()
BEGIN
  DECLARE i INT DEFAULT 1;
  WHILE i<50000 DO
  INSERT INTO emp VALUES (NULL,'a',i,1);
  SET i=i+1;
  END WHILE;
END$

在这里插入图片描述
其中age字段没有索引,查询age为45000的记录

SELECT * FROM emp WHERE age=45000;

给age添加索引后,再次查询age为45000的记录,可见查询时间变少了(如果数据更多体现的越明显,插入数据太耗时,所以我只插入了5万条)。

CREATE INDEX idx ON emp(age);

在这里插入图片描述


避免索引失效

准备环境

create table `tb_seller` (
	`sellerid` varchar (100),
	`name` varchar (100),
	`nickname` varchar (50),
	`password` varchar (60),
	`status` varchar (1),
	`address` varchar (100),
	`createtime` datetime,
    primary key(`sellerid`)
)engine=innodb default charset=utf8mb4; 

insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');


create index idx_seller_name_sta_addr on tb_seller(name,status,address);

全值匹配
对索引中所有列都指定具体值。

EXPLAIN
 SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS ='1' AND address='西安市';

可见索引生效:
在这里插入图片描述


最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
举一个上楼的例子,第几列就是第几次层楼,都必须要从第一层开始,可以到第n层,但中间不能跳跃。
匹配最左前缀法则,走索引:

EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS ='1';

在这里插入图片描述


违法最左前缀法则 , 索引失效:

EXPLAIN SELECT * FROM tb_seller WHERE STATUS =1;

在这里插入图片描述


如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:

EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米科技' AND address='西安市';

在这里插入图片描述


范围查询右边的列,索引失效
将等于改为大于号:

EXPLAIN 
SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS >'1' AND address='西安市';

在这里插入图片描述


在索引列上进行运算操作, 索引将失效。

EXPLAIN 
SELECT * FROM tb_seller WHERE SUBSTR(NAME,1) = '小米科技' ;

在这里插入图片描述


字符串不加单引号,MySQL的查询优化器会自动的进行类型转换,造成索引失效。

EXPLAIN
 SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS = 1 AND address='西安市';

在这里插入图片描述


尽量使用覆盖索引,减少select * ,即要查询的字段全部被索引包含
在这里插入图片描述


用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
在这里插入图片描述


以%开头的Like模糊查询,索引失效。
解决方法:使用覆盖索引。
在这里插入图片描述


如果MySQL评估使用索引比全表更慢,则不使用索引
给address创建一个单列索引

CREATE INDEX idx_addr ON tb_seller(address);

分别对address为北京市和西安市的记录进行查询
在这里插入图片描述
可见address为北京市时并没有使用索引,因为12条记录中11条address的值为北京市,所以不如使用全表扫描。即占少部分的记录使用索引,占大部分的使用全表扫描。


尽量使用复合索引,而少使用单列索引 。
创建复合索引

create index idx_name_sta_address on tb_seller(name, status, address);

就相当于创建了三个索引 : 
	name
	name + status
	name + status + address

创建单列索引

create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);

当查询条件包含上述三个字段的值时,数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。


查看索引使用情况

查看当前会话
SHOW STATUS LIKE 'Handler_read%';	
查看全局
SHOW GLOBAL STATUS LIKE 'Handler_read%';	

在这里插入图片描述

  • Handler_read_first:索引中第一条被读的次数。如果较高,表示服务器正执行大量全索引扫描(这个值越低越好)。
  • Handler_read_key:如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好)。
  • Handler_read_next :按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。
  • Handler_read_last:访问索引的上一条数据,实际上也是封装的ha_innobase::general_fetch函数,用于ORDER BY DESC 索引扫描避免排序
  • Handler_read_prev:按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY … DESC。
  • Handler_read_rnd :根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。
  • Handler_read_rnd_next:在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。

发布了66 篇原创文章 · 获赞 302 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_41112238/article/details/104004400