简介

Mysql服务

启动和停止

查看状态:service mysql status
启动服务:service mysql start
停止服务:service mysql stop
重启服务:service mysql restart
启动之后,查看进程:ps -ef|grep mysql

安装位置

1645747541009

修改字符集

常用命令

show databases 列出所有数据库 
create database 库名 创建一个数据库 
create database 库名 character set utf8 创建数据库,顺便执行字符集为 utf-8 
show create database 库名 查看数据库的字符集 
show variables like ‘%char%’ 查询所有跟字符集相关的信息 
set [字符集属性]=utf8 设置相应的属性为 utf8 只是临时修改,当前有效。服务重启后, 失效。 
alter database 库名 character set 'utf8' 修改数据库的字符集 
alter table 表 名 convert to character set 'utf8

永久修改

1)修改配置文件

在/usr/share/mysql/ 中找到 my.cnf 的配置文件,拷贝其中的 my-huge.cnf 到 /etc/ 并命名为 my.cnf 。添加以下内容后再重启服务。

注意:必须将文件拷贝到指定路径,且名称为 my.cnf

[client]
default-character-set=utf8
[mysqld]
character_set_server=utf8
character_set_client=utf8
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8

注意:已经创建的数据库的设定不会发生变化,参数修改只对新建的数据库有效!

2)修改已创建库、表字符集

修改数据库的字符集 
alter database mydb character set 'utf8'; 

修改数据表的字符集 
alter table mytbl convert to character set 'utf8';

3)修改已经乱码数据

无论是修改 mysql 配置文件或是修改库、表字符集,都无法改变已经变成乱码的数据。 只能删除数据重新插入或更新数据才可以完全解决

大小写不敏感

①查看大小写是否敏感:

show variables like ‘%lower_case_table_names%’ windows

系统默认大小写不敏感,但是 linux 系统是大小写敏感的

②设置大小写不敏感:

在 my.cnf 这个配置文件 [mysqld] 中加入 lower_case_table_names = 1 ,然后重启服务器

0 大小写敏感 
1 大小写不敏感。创建的表,数据库都是以小写形式存放在磁盘上,对于 sql 语句都是转换为 小写对表和 DB 进行查找 
2 创建的表和 DB 依据语句上格式存放,凡是查找都是转换为小写进行

注意:如果要设置属性为大小写不敏感,要在重启数据库实例之前就需要将原来的数据库和表转换为小写,否则将 找不到数据库名。在进行数据库参数设置之前,需要掌握这个参数带来的影响,切不可盲目设置。

sql_mode

sql_mode 定义了对 Mysql 中 sql 语句语法的校验规则

sql_mode 是个很容易被忽视的变量,默认值是空值,在这种设置下是可以允许一些非法操作的,比如允许一些非法数据的插入。在生产环境必须将这个值设置为严格模式,所以开发、测试环境的数据库也必须要设置,这样在开发测试阶段就可以发现问题。

常用的值

ONLY_FULL_GROUP_BY 
对于 GROUP BY 聚合操作,如果在 SELECT 中的列,没有在 GROUP BY 中出现,那么这个 SQL 是不合法的,因为列不在 GROUP BY 从句中 

NO_AUTO_VALUE_ON_ZERO 
该值影响自增长列的插入。默认设置下,插入 0 或 NULL代表生成下一个自增长值。如果用户希望插入的值为 0,而该列又是自增长的,那么这个选项就有用了

STRICT_TRANS_TABLES 
在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做限制

NO_ZERO_IN_DATE 
在严格模式下,不允许日期和月份为零 

NO_ZERO_DATE 
设置该值,mysql 数据库不允许插入零日期,插入零日期会抛出错误而不是警告

ERROR_FOR_DIVISION_BY_ZERO 
在INSERT或UPDATE过程中,如果数据被零除,则产生错误而非警告。如果未给出该模式,那么数据被零除时MySQL返回 NULL

NO_AUTO_CREATE_USER 
禁止 GRANT 创建密码为空的用户

NO_ENGINE_SUBSTITUTION 
如果需要的存储引擎被禁用或未编译,那么抛出错误。不设置此值时,用默认的存储引擎替代,并抛出一个异常

PIPES_AS_CONCAT 
将"||"视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似

ANSI_QUOTES 
启用 ANSI_QUOTES 后,不能用双引号来引用字符串,因为它被解释为识别符

ORACLE设置等同于PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER

查看和修改

①查看当前的 sql_mode:

select @@sql_mode; 

②sql_mode 的影响案例:group by 查询语法错误!

CREATE TABLE mytbl2 (id INT,NAME VARCHAR(200),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); 

查询每个 dept 中年龄最大的人:
SELECT NAME,dept,MAX(age) FROM mytbl2 GROUP BY dept; 

正确写法:
SELECT id,name,ab.dept,ab.maxage FROM mytbl2 m 
INNER JOIN(SELECT dept,MAX(age) maxage FROM mytbl2 GROUP BY dept) ab 
ON ab.dept=m.dept AND m.age=ab.maxage;

③临时修改 sql_mode:

set @@sql_mode=’’;

④永久修改,需要在配置文件 my.cnf 中修改:

[mysqld] 下添加 sql_mode=’’ 然后重启 mysql 即可

用户和权限管理

用户管理

相关命令

create user zhang3 identified by '123123'; 
创建名称为 zhang3 的用户,密码设为 123123; 

select host,user,password,select_priv,insert_priv,drop_priv from mysql.user; 
查看用户和权限的相关信息 

set password=password('123456') 
修改当前用户的密码 

update mysql.user set password=password('123456') where user='li4'; 
修改其他用户的密码
所有通过 user 表的修改,必须用flush privileges;命令才能生 效 

update mysql.user set user='li4' where user='wang5'; 
修改用户名
所有通过 user 表的修改,必须 用 flush privileges;命令才能生 效 

drop user li4 
删除用户
不要通过delete from user u where user='li4'进行删除,系统会有残留信息保留。

示例说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0L5D2tde-1648184325575)(Pic/1645753289513.png)]

host :表示连接类型 
    %  表示所有远程通过TCP方式的连接 
    IP地址  如 (192.168.1.2,127.0.0.1) 通过制定 ip 地址进行的 TCP 方式的连接 
    机器名  通过制定 ip 网络中的机器名进行的TCP方式的连接 
    ::1  IPv6的本地ip地址,等同于IPv4的127.0.0.1 
    localhost 本地方式通过命令行方式的连接 ,比如 mysql -u xxx -p 123xxx 方式的连接。 

user:表示用户名 
	同一用户通过不同方式链接的权限是不一样的。 
	
password:密码 
	所有密码串通过password(明文字符串)生成的密文字符串。加密算法为MYSQLSHA1,不可逆。 mysql5.7的密码保存到 authentication_string 字段中不再使用 password 字段。 

select_priv,insert_priv等 
	为该用户所拥有的权限。

权限管理

授予权限

grant 权限 1,权限 2,…权限 n on 数据库名称.表名称 to 用户名@用户地址 identified by ‘连接口令’ 
该权限如果发现没有该用户,则会直接新建一个用户。 

示例: 
grant select,insert,delete,drop on atguigudb.* to li4@localhost; 
给 li4 用户用本地命令行方式下,授予 atguigudb 这个库下的所有表的插删改查的权限。 

grant all privileges on *.* to joe@'%' identified by '123'; 
授予通过网络方式登录的的 joe 用户 ,对所有库所有表的全部权限,密码设为 123.

收回权限

show grants 
查看当前用户权限 

revoke [权限 1,权限 2,…权限 n] on 库名.表名 from 用户名@用户地址 ; 
收回权限命令 

REVOKE ALL PRIVILEGES ON mysql.* FROM joe@localhost; 
收回全库全表的所有权限 

REVOKE select,insert,update,delete ON mysql.* FROM joe@localhost; 
收回 mysql 库下的所有表的插删改查权限

权限收回后,必须用户重新登录后,才能生效。

查看权限

show grants; 
查看当前用户权限 

select * from user ;

逻辑架构

整体架构图

1645753599762

和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可 以根据业务的需求和实际需要选择合适的存储引擎。

连接层

最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的 通信。主要完成一些类似于连接处理、授权认证及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

服务层

Management Serveices & Utilities 
即系统管理和控制工具

SQL Interface 即SQL接口
接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface 

Parser解析器
SQL命令传递到解析器的时候会被解析器验证和解析 

Optimizer查询优化器
SQL语句在查询之前会使用查询优化器对查询进行优化,比如有 where 条件时,优化器来决定先投影还是先过滤。 

Cache和Buffer查询缓存
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等

引擎层

存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同 的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。

存储层

数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。

show profile

利用 show profile 可以查看 sql 的执行周期!

开启 profile

查看 profile 是否开启:

show variables like '%profiling%

如果没有开启,可以执行 set profiling=1 开启!

使用 profile

执行 show prifiles 命令,可以查看最近的几次查询。

根据 Query_ID,可以进一步执行 show profile cpu,block io for query Query_id 来查看 sql 的具体执行步骤。

查询流程

mysql 的查询流程大致是:

mysql 客户端通过协议与 mysql 服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果, 否则进行语句解析,也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储 SELECT 语句以及相应的查询结果集。如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。

语法解析器和预处理:首先 mysql 通过关键字将 SQL 语句进行解析,并生成一颗对应的“解析树”。mysql 解析器将使用 mysql 语法规则验证和解析查询;预处理器则根据一些 mysql 规则进一步检查解析数是否合法。

查询优化器当解析树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式, 最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。

然后,mysql 默认使用的 BTREE 索引,并且一个大致方向是:无论怎么折腾 sql,至少在目前来说,mysql 最多只 用到表中的一个索引。

SQL 的执行顺序

手写的顺序:

1645753876704

真正执行的顺序:

随着 Mysql 版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。下面是经常出现的查询顺序:

1645753914380 1645753945005

SQL回顾

常见的 Join 查询图

1645754119515

Join 示例

建表语句

CREATE TABLE `t_dept` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`deptName` VARCHAR(30) DEFAULT NULL,
	`address` VARCHAR(40) DEFAULT NULL,
	PRIMARY KEY (`id`)
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARSET = utf8;

CREATE TABLE `t_emp` (
	`id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(20) DEFAULT NULL,
	`age` INT(3) DEFAULT NULL,
	`deptId` INT(11) DEFAULT NULL,
	empno int NOT NULL,
	PRIMARY KEY (`id`),
	KEY `idx_dept_id` (`deptId`)
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARSET = utf8;

INSERT INTO t_dept (deptName, address)
VALUES ('华山', '华山');

INSERT INTO t_dept (deptName, address)
VALUES ('丐帮', '洛阳');

INSERT INTO t_dept (deptName, address)
VALUES ('峨眉', '峨眉山');

INSERT INTO t_dept (deptName, address)
VALUES ('武当', '武当山');

INSERT INTO t_dept (deptName, address)
VALUES ('明教', '光明顶');

INSERT INTO t_dept (deptName, address)
VALUES ('少林', '少林寺');

------------------------------------------------------

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('风清扬', 90, 1, 100001);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('岳不群', 50, 1, 100002);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('令狐冲', 24, 1, 100003);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('洪七公', 70, 2, 100004);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('乔峰', 35, 2, 100005);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('灭绝师太', 70, 3, 100006);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('周芷若', 20, 3, 100007);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('张三丰', 100, 4, 100008);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('张无忌', 25, 5, 100009);

INSERT INTO t_emp (NAME, age, deptId, empno)
VALUES ('韦小宝', 18, NULL, 100010);

案例

结合上面的查询图

表一:门派表
门派id,门派名,门派地址

表二:成员表
成员id,姓名,年龄,门派id,成员号
1.所有有门派人员的信息(要求显示门派名称) 
SELECT e.`name`,d.`deptName` FROM t_emp e INNER JOIN t_dept d ON e.`deptId`=d.`id`; 

2. 列出所有人员及其门派信息 
SELECT e.`name`,d.`deptName` FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`; 

3. 列出所有门派 
SELECT * FROM t_dept; 

4. 所有无门派人士 
SELECT * FROM t_emp WHERE deptId IS NULL; 

5. 所有无人门派 
SELECT d.* FROM t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL; 

6. 所有人员和门派的对应关系 
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id` 
UNION 
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.`deptId`=d.`id`;

7. 所有没有入门派的人员和没人入的门派 
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id` WHERE e.deptId IS NULL 
UNION 
SELECT * FROM t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL; 

8. 添加 CEO 字段 
ALTER TABLE `t_dept` add CEO INT(11) ; 
update t_dept set CEO=2 where id=1; 
update t_dept set CEO=4 where id=2; 
update t_dept set CEO=6 where id=3; 
update t_dept set CEO=8 where id=4; 
update t_dept set CEO=9 where id=5; 
8.1 求各个门派对应的掌门人名称 
SELECT d.deptName,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id 
8.2 求所有当上掌门人的平均年龄 
SELECT AVG(e.age) FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id 

8.3 求所有人物对应的掌门名称 
SELECT ed.name '人物',c.name '掌门' FROM (SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed LEFT JOIN t_emp c on ed.ceo= c.id; 

SELECT e.name '人物',tmp.name '掌门' FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp ON e.deptId=tmp.did; 

SELECT e1.name '人物',e2.name '掌门' FROM t_emp e1 LEFT JOIN t_dept d on e1.deptid = d.id LEFT JOIN t_emp e2 on d.ceo = e2.id ;

SELECT e2.name '人物', (SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门' from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;

索引优化

概念

是什么

MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质: 索引是数据结构。可以简单理解为排好序的快速查找数据结构。

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。

下图就是一种可能的索引方式示例:

1645755182761

左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

优缺点

优势:

提高数据检索的效率,降低数据库的IO成本。

通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

劣势:

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间 的。

Mysql索引

Btree 索引

MySQL 使用的是 Btree 索引。

1645755264759

【初始化介绍】

一颗 b 树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色 所示),如磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3, P1 表示小于 17 的磁盘块,P2 表示在 17 和 35 之间的磁盘块,P3 表示大于 35 的磁盘块。 真实的数据存在于叶子节点即 3、5、9、10、13、15、28、29、36、60、75、79、90、99。 非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如 17、35 并不真实存在于数据表中。

【查找过程】

如果要查找数据项 29,那么首先会把磁盘块 1 由磁盘加载到内存,此时发生一次 IO,在内存中用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,内存时间因为非常短(相比磁盘的 IO)可以忽略不计,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 由磁盘加载到内存,发生第二次 IO,29 在 26 和 30 之间,锁定磁盘块 3 的 P2 指 针,通过指针加载磁盘块 8 到内存,发生第三次 IO,同时内存中做二分查找找到 29,结束查询,总计三次 IO。

真实的情况是,3 层的 b+树可以表示上百万的数据,如果上百万的数据查找只需要三次 IO,性能提高将是巨大的, 如果没有索引,每个数据项都要发生一次 IO,那么总共需要百万次的 IO,显然成本非常非常高。

B+tree 索引

1645755334996

B+Tree 与 B-Tree 的区别

1)B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。

2)在 B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而 B+树中每个记录 的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看 B- 树的性能好像要比 B+树好,而在实际应用中却是 B+树的性能要好些。因为 B+树的非叶子节点不存放实际的数据, 这样每个节点可容纳的元素个数比 B-树多,树高比 B-树小,这样带来的好处是减少磁盘访问次数。尽管 B+树找到 一个记录所需的比较次数要比 B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中 B+树的性能可能还会好些,而且 B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有 文件,一个表中的所有记录等),这也是很多数据库和文件系统使用 B+树的缘故。

思考:

为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?

  1. B+树的磁盘读写代价更低

B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就 越多。相对来说 IO 读写次数也就降低了。

  1. B+树的查询效率更加稳定

由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

聚簇索引和非聚簇索引

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语‘聚簇’表示数据行和相邻的键值聚簇的存储在一起。如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。

1645755431303

聚簇索引的好处:

按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不不用从多 个数据块中提取数据,所以节省了大量的 io 操作。

聚簇索引的限制:

对于 mysql 数据库目前只有 innodb 数据引擎支持聚簇索引,而 Myisam 并不支持聚簇索引。 由于数据物理存储排序方式只能有一种,所以每个 Mysql 的表只能有一个聚簇索引。一般情况下就是该表的主键。 为了充分利用聚簇索引的聚簇的特性,所以 innodb 表的主键列尽量选用有序的顺序 id,而不建议用无序的 id,比如 uuid 这种。

索引分类

单值索引

概念:即一个索引只包含单个列,一个表可以有多个单列索引语法

随表一起创建: 
CREATE TABLE customer (
	id INT(10) UNSIGNED AUTO_INCREMENT,
	customer_no VARCHAR(200),
	customer_name VARCHAR(200),
	PRIMARY KEY (id),
	KEY (customer_name)
);

单独建单值索引: 
CREATE INDEX idx_customer_name ON customer(customer_name);

唯一索引

概念:索引列的值必须唯一,但允许有空值

随表一起创建: 
CREATE TABLE customer (
	id INT(10) UNSIGNED AUTO_INCREMENT,
	customer_no VARCHAR(200),
	customer_name VARCHAR(200),
	PRIMARY KEY (id),
	KEY (customer_name),
	UNIQUE (customer_no)
);

单独建唯一索引: 
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);

主键索引

概念:设定为主键后数据库会自动建立索引,innodb为聚簇索引

随表一起建索引
CREATE TABLE customer (
	id INT(10) UNSIGNED AUTO_INCREMENT,
	customer_no VARCHAR(200),
	customer_name VARCHAR(200),
	PRIMARY KEY (id)
);

单独建主键索引: 
ALTER TABLE customer add PRIMARY KEY customer(customer_no); 

删除建主键索引: 
ALTER TABLE customer drop PRIMARY KEY ; 

修改建主键索引: 必须先删除掉(drop)原索引,再新建(add)索引

复合索引

概念:即一个索引包含多个列

随表一起建索引: 
CREATE TABLE customer (
	id INT(10) UNSIGNED AUTO_INCREMENT,
	customer_no VARCHAR(200),
	customer_name VARCHAR(200),
	PRIMARY KEY (id),
	KEY (customer_name),
	UNIQUE (customer_name),
	KEY (customer_no, customer_name)
);

单独建索引: 
CREATE INDEX idx_no_name ON customer(customer_no,customer_name);

基本语法

创建 
CREATE [UNIQUE ] INDEX [indexName] ON table_name(column)) 

删除 
DROP INDEX [indexName] ON mytable; 

查看 
SHOW INDEX FROM table_name\G 

使用Alter命令 
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list) : 该语句添加一个主键,这意味着索引值必须是唯一 的,且不能为 NULL。 
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。 
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。

创建时机

适合创建索引的情况

主键自动建立唯一索引;

频繁作为查询条件的字段应该创建索引

查询中与其它表关联的字段,外键关系建立索引

单键/组合索引的选择问题, 组合索引性价比更高

查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

查询中统计或者分组字段

不适合创建索引的情况

表记录太少

经常增删改的表或者字段

Where 条件里用不到的字段不创建索引

过滤性不好的不适合建索引

索引失效

全值匹配我最爱

  • 有以下 SQL 语句
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'
  • 建立索引

CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);

1645776287682

结论:全职匹配我最爱指的是,查询的字段按照顺序在索引中都可以匹配到!

1645776311928

SQL 中查询字段的顺序,跟使用索引中字段的顺序没有关系。优化器会在不影响 SQL 执行结果的前提下,给你自动地优化。

最佳左前缀法则

1645776359826

查询字段与索引字段顺序的不同会导致索引无法充分使用,甚至索引失效!

原因:使用复合索引,需要遵循最佳左前缀法则,即如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列

结论:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。

不要在索引列上做任何计算

不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描。

  • 在查询列上使用了函数
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age=30; 
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(age,3)=30;
1645776428250

结论:等号左边无计算!

  • 在查询列上做了转换
create index idx_name on emp(name); 
explain select sql_no_cache * from emp where name='30000'; 
explain select sql_no_cache * from emp where name=30000; 

字符串不加单引号,则会在 name 列上做一次转换!

1645776518435

结论:等号右边无转换!

索引列上不能有范围查询

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=5 AND emp.name = 'abcd'; 
explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid<=5 AND emp.name = 'abcd';
1645778850240

建议:将可能做范围查询的字段的索引顺序放在最后

尽量使用覆盖索引

即查询列和索引列一致,不要写 select *!

explain SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt'; 
explain SELECT SQL_NO_CACHE age,deptId,name FROM emp WHERE emp.age=30 and deptId=4 and name='XamgXt';
1645778893884

使用不等于(!= 或者<>)的时候

mysql 在使用不等于(!= 或者<>)时,有时会无法使用索引会导致全表扫描。

1645778930799

字段的is not null和is null

1645778955398

当字段允许为 Null 的条件下:

1645778975110

is not null 用不到索引,is null 可以用到索引。

like的前后模糊匹配

1645779014008

前缀不能出现模糊匹配!

减少使用or

1645779056022

使用union all或者union来替代:

1645779079503

口诀

全职匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE 百分写最右,覆盖索引不写*;
不等空值还有 OR,索引影响要注意;
VAR 引号不可丢,SQL 优化有诀窍。

Explain性能分析

概念

使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理你的 SQL 语句的。分析你的查询语句或是表结构的性能瓶颈。

用法: Explain+SQL 语句。

Explain 执行后返回的信息:

1645771470044

准备工作

CREATE TABLE t1 (
	id INT(10) AUTO_INCREMENT,
	content VARCHAR(100) NULL,
	PRIMARY KEY (id)
);

CREATE TABLE t2 (
	id INT(10) AUTO_INCREMENT,
	content VARCHAR(100) NULL,
	PRIMARY KEY (id)
);

CREATE TABLE t3 (
	id INT(10) AUTO_INCREMENT,
	content VARCHAR(100) NULL,
	PRIMARY KEY (id)
);

CREATE TABLE t4 (
	id INT(10) AUTO_INCREMENT,
	content VARCHAR(100) NULL,
	PRIMARY KEY (id)
);

INSERT INTO t1 (content)
VALUES (CONCAT('t1_', FLOOR(1 + RAND() * 1000)));

INSERT INTO t2 (content)
VALUES (CONCAT('t2_', FLOOR(1 + RAND() * 1000)));

INSERT INTO t3 (content)
VALUES (CONCAT('t3_', FLOOR(1 + RAND() * 1000)));

INSERT INTO t4 (content)
VALUES (CONCAT('t4_', FLOOR(1 + RAND() * 1000)));

返回信息

id

select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。

①id 相同,执行顺序由上至下

1645771594461

②id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行

1645771668756

③有相同也有不同

1645771695359

id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行衍生(DERIVED )

关注点:id 号每个号码,表示一趟独立的查询。一个 sql 的查询趟数越少越好。

select_type

select_type 代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。

1645771789672
  • SIMPLE

SIMPLE 代表单表查询;

1645771824421
  • PRIMARY

查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary。

1645771860834
  • DERIVED

在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询, 把结果放在临时表里。

  • SUBQUERY

在 SELECT 或 WHERE 列表中包含了子查询。

1645771900909
  • DEPENDENT SUBQUERY

在 SELECT 或 WHERE 列表中包含了子查询,子查询基于外层。

1645771928671

都是 where 后面的条件,subquery 是单个值,dependent subquery 是一组值。

  • UNCACHEABLE SUBQUREY
1645771977956

当使用了@@来引用系统变量的时候,不会使用缓存。

  • UNION

若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;若 UNION 包含在 FROM 子句的子查询中,外层 SELECT 将被标记为:DERIVED。

1645772016018
  • UNION RESULT

从 UNION 表获取结果的 SELECT。

table

这个数据是基于哪张表的。

type

type 是查询的访问类型。是较为重要的一个指标,结果值从最好到最坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL ,一般来说,得保证查询至少达到 range 级别,最好能达到 ref。

  • system

表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计

  • const

表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快 ,

如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。

1645772107908
  • eq_ref

唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。

1645772133948
  • ref

非唯一性索引扫描,返回匹配某个单独值的所有行.本质上也是一种索引访问,它返回所有匹配某个单独值的行,

然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。

没用索引前:

1645772162132

建立索引后:

1645772186693
  • ref_or_null

对于某个字段既需要关联条件,也需要 null 值得情况下。查询优化器会选择用 ref_or_null 连接查询。

1645772465851
  • index_merge

在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的 sql 中。

1645772435974
  • unique_subquery

该联接类型类似于index_subquery。 子查询中的唯一索引。

1645772533476
  • index_subquery

利用索引来关联子查询,不再全表扫描。

1645772502106
  • range

只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现

了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而

结束语另一点,不用扫描全部索引。

1645772220237 1645772249938
  • index

出现index是sql使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。

1645772321320
  • all

Full Table Scan,将遍历全表以找到匹配的行。

1645772348637

备注:一般来说,得保证查询至少达到range级别,最好能达到ref。

possible_keys

显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一

定被查询实际使用。

key

实际使用的索引。如果为NULL,则没有使用索引。

key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的

利用上了索引。ken_len 越长,说明索引使用的越充分。

1645772597245 1645772610332

如何计算:

①先看索引上字段的类型+长度比如 int=4 ; varchar =20 ; char(20) =20

②如果是 varchar 或者 char 这种字符串字段,视字符集要乘不同的值,比如 utf-8 要乘 3,GBK 要乘 2,

③varchar 这种动态字符串要加 2 个字节

④允许为空的字段要加 1 个字节

第一组:key_len=age 的字节长度+name 的字节长度=4+1 + ( 20*3+2)=5+62=67

第二组:key_len=age 的字节长度=4+1=5

1645772649023

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

1645772687746[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eF3Zf9Jq-1648184325581)(Pic/1645772705579.png)]

rows

rows列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LxnCXOkl-1648184325583)(Pic/1645772705579.png)]

Extra

其他的额外重要的信息。

  • Using filesort

说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引

完成的排序操作称为“文件排序”。

出现 filesort 的情况:

1645772747201

优化后,不再出现 filesort 的情况:

1645772773823

查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。

  • Using temporary

使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group

by。

优化前:

1645772810020

优化后:

1645772838804
  • Using index

Using index 代表表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错! 如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找。

利用索引进行了排序或分组。

  • Using where

表明使用了 where 过滤。

  • Using join buffer
1645772889965

使用了连接缓存。

  • impossible where

where 子句的值总是 false,不能用来获取任何元组。

1645772924412
  • select tables optimized away

在没有 GROUPBY 子句的情况下,基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*)操

作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

在 innodb 中:

1645772961404

在 Myisam 中:

1645772982583

批量数据脚本

插入数据

建表语句

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 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`)
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARSET = utf8;

设置参数

在执行创建函数之前,首先请保证 log_bin_trust_function_creators 参数为 1,即 on 开启状态,否则会报错

查询:show variables like ‘log_bin_trust_function_creators’;

设置:set global log_bin_trust_function_creators=1;

当然,如上设置只存在于当前操作,想要永久生效,需要写入到配置文件中: 在[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 $$ 

如果要删除函数,则执行: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$$

如果要删除函数:drop function rand_num;

创建存储过程

  • 创建往 emp 表中插入数据的存储过程
DELIMITER $$ 
CREATE PROCEDURE insert_emp( START INT , max_num INT ) 
BEGIN 
    DECLARE i INT DEFAULT 0; 
    #set autocommit =0 把 autocommit 设置成 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 万条数据 
DELIMITER ; 
CALL insert_dept(10000);
  • 添加数据到员工表
#执行存储过程,往 emp 表添加 50 万条数据 
DELIMITER ; 
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=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(“dbname”,“tablename”);

查询优化

关联查询优化

建表语句

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));

案例

表一:class
id,card

表二:book
bookid,card
  • left join

①EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

1645779261505

②如何优化?在哪个表上建立索引?

ALTER TABLE book ADD INDEX idx_card( card);

1645779285350

③删除 book 表的索引:drop index idx_card on book;

在 class 表上建立索引:alter table class add index idx_card(card);

1645779309604

结论:

①在优化关联查询时,只有在被驱动表上建立索引才有效!

②left join 时,左侧的为驱动表,右侧为被驱动表!

  • inner join

①EXPLAIN SELECT * FROM book inner join class on class.card=book.card;

1645779350648

②两个查询字段调换顺序,发现结果也是一样的!

1645779375252

③在 book 表中,删除 9 条记录

1645779400905

④结论:inner join 时,mysql 会自己帮你把小结果集的表选为驱动表。

⑤straight_join: 效果和 inner join一样,但是会强制将左侧作为驱动表!

1645779421967

四个关联查询案例分析

EXPLAIN SELECT ed.name '人物',c.name '掌门' FROM 
(SELECT e.name,d.ceo from t_emp e LEFT JOIN t_dept d on e.deptid=d.id) ed 
LEFT JOIN t_emp c on ed.ceo= c.id;
1645779467066
EXPLAIN SELECT e.name '人物',tmp.name '掌门' 
FROM t_emp e LEFT JOIN (SELECT d.id did,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id)tmp 
ON e.deptId=tmp.did;
1645779501792

上述两个案例,第一个查询效率较高,且有优化的余地。第二个案例中,子查询作为被驱动表,由于子查询是虚表, 无法建立索引,因此不能优化。

结论

子查询尽量不要放在被驱动表,有可能使用不到索引;

left join时,尽量让实体表作为被驱动表。

EXPLAIN SELECT e1.name '人物',e2.name '掌门' 
FROM t_emp e1 
LEFT JOIN t_dept d on e1.deptid = d.id 
LEFT JOIN t_emp e2 on d.ceo = e2.id ;
1645779557612
Explain SELECT e2.name '人物', 
(SELECT e1.name FROM t_emp e1 where e1.id= d.ceo) '掌门' 
from t_emp e2 LEFT JOIN t_dept d on e2.deptid=d.id;
1645779589054

结论:能够直接多表关联的尽量直接关联,不用子查询!

子查询优化

案例

取所有不为掌门人的员工,按年龄分组!

select age as '年龄', count(*) as '人数' from t_emp where id not in
(select ceo from t_dept where ceo is not null) group by age;
1645779647369

如何优化?

①解决 dept 表的全表扫描,建立 ceo 字段的索引:

1645779672959

此时,再次查询:

1645779694188

②进一步优化,替换掉not in。

上述 SQL 可以替换为:

select age as '年龄',count(*) as '人数' from emp e left join dept d on e.id=d.ceo where d.id is null group by age;
1645779731368

结论: 在范围判断时,尽量不要使用 not in 和 not exists,使用 left join on xxx is null 代替。

排序分组优化

where 条件和 on 的判断这些过滤条件,作为优先优化的部门,是要被先考虑的!其次,如果有分组和排序,那么也要考虑 grouo by 和 order by。

无过滤不索引

create index idx_age_deptid_name on emp (age,deptid,name); 
explain select * from emp where age=40 order by deptid; 
explain select * from emp order by age,deptid; 
explain select * from emp order by age,deptid limit 10;
1645779820114

using filesort 说明进行了手工排序!原因在于没有 where 作为过滤条件!

1645779841751

结论: 无过滤,不索引。where,limt 都相当于一种过滤条件,所以才能使用上索引!

顺序错,必排序

①explain select * from emp where age=45 order by deptid,name;

1645779902107

②explain select * from emp where age=45 order by deptid,empno;

1645779918049

empno 字段并没有建立索引,因此也无法用到索引,此字段需要排序!

③explain select * from emp where age=45 order by name,deptid;

1645779937845

where 两侧列的顺序可以变换,效果相同,但是 order by 列的顺序不能随便变换!

④explain select * from emp where deptid=45 order by age;

1645779955469

deptid 作为过滤条件的字段,无法使用索引,因此排序没法用上索引

方向反,必排序

①explain select * from emp where age=45 order by deptid desc, name desc ;

1645779995744

如果可以用上索引的字段都使用正序或者逆序,实际上是没有任何影响的,无非将结果集调换顺序。

②explain select * from emp where age=45 order by deptid asc, name desc ;

1645780017319

如果排序的字段,顺序有差异,就需要将差异的部分,进行一次倒置顺序,因此还是需要手动排序的!

索引的选择

①首先,清除 emp 上面的所有索引,只保留主键索引!

drop index idx_age_deptid_name on emp;

②查询:年龄为 30 岁的,且员工编号小于 101000 的用户,按用户名称排序

explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

1645780213210

③全表扫描肯定是不被允许的,因此我们要考虑优化。

思路:首先需要让 where 的过滤条件,用上索引;

查询中,age、empno 是查询的过滤条件,而 name 则是排序的字段,因此我们来创建一个此三个字段的复合索引:

create index idx_age_empno_name on emp(age,empno,name);

1645780233219

再次查询,发现 using filesort 依然存在。

原因: empno 是范围查询,因此导致了索引失效,所以 name 字段无法使用索引排序。

所以,三个字段的符合索引,没有意义,因为 empno 和 name 字段只能选择其一!

④解决: 鱼与熊掌不可兼得,因此,要么选择 empno,要么选择 name

drop index idx_age_empno_name on emp;

create index idx_age_name on emp(age,name);

create index idx_age_empno on emp(age,empno);

两个索引同时存在,mysql 会选择哪个?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HmyGJzGe-1648184325586)(Pic/1645780263920.png)]

explain SELECT SQL_NO_CACHE * FROM emp use index(idx_age_name) WHERE age =30 AND empno <101000 ORDER BY NAME ;

1645780288197

原因:所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序 ,其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没 有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,使用 empno 字段的范围查询,过滤性更好

(empno 从 100000 开始)!

结论: 当范围条件和 group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的

数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。

using filesort

  • mysql 的排序算法

①双路排序

MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和 orderby 列,对他

们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。

从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段。

简单来说,取一批数据,要对磁盘进行了两次扫描,众所周知,I\O 是很耗时的,所以在 mysql4.1 之后,出现

了第二种改进的算法,就是单路排序。

②单路排序

从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出,

它的效率更快一些,避免了第二次读取数据。并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间,

因为它把每一行都保存在内存中了。

单路排序的问题

由于单路是后出的,总体而言好过双路。但是存在以下问题:

在 sort_buffer 中,方法 B 比方法 A 要多占用很多空间,因为方法 B 是把所有字段都取出, 所以有可能取出的数

据的总大小超出了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据,进行排序(创建 tmp 文件,多 路合并),排完再取取 sort_buffer 容量大小,再排……从而多次 I/O。

结论:本来想省一次 I/O 操作,反而导致了大量的 I/O 操作,反而得不偿失。

  • 如何优化

①增大 sort_butter_size 参数的设置

不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进

程的 1M-8M 之间调整。

②增大 max_length_for_sort_data 参数的设置

mysql 使用单路排序的前提是排序的字段大小要小于 max_length_for_sort_data。

提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大,

明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。

③减少 select 后面的查询的字段。

当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的

算法——单路排序, 否则用老算法——多路排序。

两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 tmp 文件进行合并排序,导致多次 I/O,

但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size。

使用覆盖索引

覆盖索引:SQL 只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。

1645780407622

group by

group by 使用索引的原则几乎跟 order by 一致 ,唯一区别是 groupby 即使没有过滤条件用到索引,也可以直

接使用索引。

1645780430138
相关练习见pdf文档

截取查询分析

慢查询日志

是什么

1)MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。

2)具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为 10,意思是运行10秒以上的语句。

3)由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。

怎么用

默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。

当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。 慢查询日志支持将日志记录写入文件。

1) 开启设置

1645780599403

2) 如永久生效需要修改配置文件 my.cnf 中[mysqld]下配置

[mysqld] 
slow_query_log=1 
slow_query_log_file=/var/lib/mysql/atguigu-slow.log 
long_query_time=3 
log_output=FILE

3) 运行查询时间长的 sql,打开慢查询日志查看

日志分析工具 mysqldumpslow

1) 查看mysqldumpslow的帮助信息

1645780699846

2) 查看mysqldumpslow的帮助信息

得到返回记录集最多的 10 个 SQL 
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log 

得到访问次数最多的 10 个 SQL 
mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log 

得到按照时间排序的前 10 条里面含有左连接的查询语句 
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log 

另外建议在使用这些命令时结合 | 和 more 使用 ,否则有可能出现爆屏情况 
mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more

SHOW PROCESSLIST

是什么

查询 mysql 进程列表,可以杀掉故障进程

怎么用

SHOW PROCESSLIST;
KILL 6;
1645780829520

主从复制

基本原理

(1)slave 会从 master 读取 binlog 来进行数据同步

(2)三步骤+原理图

1645780930114

MySQL 复制过程分成三步:

master 将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;

slave 将 master 的 binary log events 拷贝到它的中继日志(relay log);

slave 重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL 复制是异步的且串行化的

复制的基本原则

(1)每个 slave 只有一个 master

(2)每个 slave 只能有一个唯一的服务器 ID

(3)每个 master 可以有多个 salve

复制的最大问题

因为发生多次 IO,存在延时问题

一主一从常见配置

(1) mysql 版本一致且后台以服务运行

(2) 主从都配置在[mysqld]结点下,都是小写

主机修改 my.ini 配置文件

1645781035809
主服务器唯一 ID 
server-id=1 
启用二进制日志
log-bin=自己本地的路径/data/mysqlbin 
log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin 
设置不要复制的数据库 
binlog-ignore-db=mysql 
设置需要复制的数据库 
binlog-do-db=需要复制的主数据库名字 
设置 logbin 格式 
binlog_format=STATEMENT(默认)

mysql 主从复制起始时,从机不继承主机数据

(3) logbin 格式

binlog_format=STATEMENT(默认) 
binlog_format=ROW 
binlog_format=MIXED
1645781110520

(4)从机配置文件修改 my.cnf 的[mysqld]栏位下

#从机服务 id 
server-id = 2 
\#注意 my.cnf 中有 server-id = 1 
\#设置中继日志 
relay-log=mysql-relay 

(5) 因修改过配置文件,请主机+从机都重启后台 mysql 服务

(6) 主机从机都关闭防火墙、安全工具(腾讯管家等)

(7) 在 Windows 主机上建立帐户并授权 slave

#创建用户,并授权 
GRANT REPLICATION SLAVE ON *.* TO '备份账号'@'从机器数据库 IP' IDENTIFIED BY '123456';
1645781192017

(8) 查询 master 的状态,并记录下 File 和 Position 的值

#查询 master 的状态 
show master status;
1645781240727

执行完此步骤后不要再操作主服务器 MYSQL,防止主服务器状态值变化

(9) 在 Linux 从机上配置需要复制的主机

#查询 master 的状态 
CHANGE MASTER TO MASTER_HOST='主机 IP',MASTER_USER='创建用户名',MASTER_PASSWORD='创建的密码', 
MASTER_LOG_FILE='File 名字',MASTER_LOG_POS=Position 数字;
1645781288959

(10) 启动从服务器复制功能

start slave; 
show slave status\G;

下面两个参数都是 Yes,则说明主从配置成功

Slave_IO_Running: Yes 
Slave_SQL_Running: Yes

(11) 主机新建库、新建表、insert 记录,从机复制

(12) 如何停止从服务复制功能

stop slave;

MYCAT

是什么

数据库中间件,前身是阿里的 cobar

做什么

(1) 读写分离

(2) 数据分片: 垂直拆分、水平拆分、垂直+水平拆分

(3) 多数据源整合

MYCAT 原理

“拦截”:Mycat 的原理中最重要的一个动词是“拦截”,它拦截了用户发送过来的 SQL 语句,首先对 SQL 语句做了一些特定的分析:如分片分析、路由分析、读写分离分析、缓存分析等,然后将此 SQL 发往后端的真实数据库,并将返回的结果做适当的处理,最终再返回给用户。

1645781485692

这种方式把数据库的分布式从代码中解耦出来,程序员察觉不出来后台使用 mycat 还是 mysql。

安装启动

(1) 解压缩文件拷贝到 linux 下 /usr/local/

(2) 三个配置文件

schema.xml 定义逻辑库,表、分片节点等内容

rule.xml 定义分片规则

server.xml 定义用户以及系统相关变量,如端口等.

(3) 启动前先修改 schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
  <!--逻辑库 name 名称, checkSQLschema sqlMaxLimit 末尾是否要加 limit xxx-->
  <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1">
  </schema>
  <!--逻辑库 name 名称, dataHost 引用的哪个 dataHost database:对应 mysql 的 database-->
  <dataNode name="dn1" dataHost="localhost1" database="db1" />
  <dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100" >
    <heartbeat>select user()</heartbeat>
    <!-- can have multi write hosts -->
    <writeHost host="hostM1" url="localhost:3306" user="root" password="123456">
    </writeHost>
  </dataHost>
</mycat:schema>

(4) 再修改 server.xml

<user name="root"> 
    <property name="password">654321</property> 
    <property name="schemas">TESTDB</property> 
</user>

(5) 启动程序

控制台启动 :去 mycat/bin 目录下 mycat console 
后台启动 :去 mycat/bin 目录下 mycat start
有关更多mycat内容见pdf