1. MySQL_简介
1.1. MySQL简介
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB(创始人Michael Widenius)公司开发,2008被Sun收购(10亿美金),2009年Sun被Oracle收购。MariaDB
MariaDB基于事务的Maria存储引擎,替换了MySQL的MyISAM存储引擎; 它使用了Percona的 XtraDB代替了InnoDB存储引擎。
MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL是开源的,所以你不需要支付额外的费用。
MySQL是可以定制的,采用了GPL(GNU General Public License)协议,你可以修改源码来开发自己的MySQL系统。
MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
MySQL使用标准的SQL数据语言形式。
MySQL可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、C#、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。
1.2. MySQL高手是怎样炼成的(开发工程师,DBA,运维)
数据库内部结构和原理
数据库建模优化
数据库索引建立
SQL语句优化
SQL编程(自定义函数、存储过程、触发器、定时任务)
mysql服务器的安装配置
数据库的性能监控分析与系统优化
各种参数常量设定
主从复制
分布式架构搭建、垂直切割和水平切割
数据迁移
容灾备份和恢复
shell或python等脚本语言开发
对开源数据库进行二次开发
2. MySQL_rpm安装
2.1. MySQL_Linux版的安装(mysql5.7)
2.1.1. 下载地址
2.1.2. 打开官网,点击DOWNLOADS
然后,点击MySQL Community(GPL) Downloads
MySQL分为社区版(Community Server)和企业版(Enterprise Server)

2.1.3. 点击MySQL Community Server

2.1.4. 在General Availability(GA) Releases中选择适合的版本
如果安装Windows 系统下MySQL ,推荐下载MSI安装程序;点击Go to Download Page进行下载即可
Windows下的MySQL安装有两种安装程序
l mysql-installer-web-community-5.7.28.0.msi 下载程序大小:19020KB;安装时需要联网安装组件。
l mysql-installer-community-5.7.28.0.msi 下载程序大小:504828KB;安装时离线安装即可。推荐。

- 如果安装Linux系统下MySQL,官方给出多种安装方式
| 安装方式 | 特点 |
|---|---|
| rpm | 安装简单,灵活性差,无法灵活选择版本、升级 |
| rpm repository | 安装包极小,版本安装简单灵活,升级方便,需要联网安装 |
| 通用二进制包 | 安装比较复杂,灵活性高,平台通用性好 |
| 源码包 | 安装最复杂,时间长,参数设置灵活,性能好 |
目前最新版本为8.0.18,发布时间2019.10.14;8.0.0在2016.9.12日就发布了。
本课程中使用5.7版本。选择下图中⑤来到以前版本下载页面。

2.1.5. 下载MySQL Community Server 5.7.28
这里不能直接选择CentOS7系统的版本,所以选择与之对应的Red Hat Enterprise Linux 7
直接点Download下载RPM Bundle全量包。包括了所有下面的组件。不需要一个一个下载了。

2.1.6. 不需要输入账号登录
- 直接点:No thanks,just start my download.

2.1.7. 下载的tar包,用压缩工具打开
![]()
- 解压后rpm安装包

2.2. CentOS7系统,检查mysql依赖环境
2.2.1. 查看是否安装过MySQL
rpm -qa | grep -i mariadb CentOS7使用这个
-i忽略大小写

卸载MySQL; --nodeps 排除依赖,否则别的软件依赖mysql,无法删除
rpm -e --nodeps mariadb-libs

2.2.2. 检查/tmp临时目权限;chmod -R 777 /tmp

2.2.3. 执行安装命令前,先执行查询命令检查依赖
rpm -qa|grep libaio
- 如果存在libaio包如下:
![]()
rpm -qa|grep net-tools
- 如果存在net-tools包如下:
![]()
- 如果不存在需要到centos安装盘里进行rpm安装。安装linux如果带图形化界面,这些都是安装好的。
2.3. CentOS7系统,安装MySQL 5.7.28
2.3.1. 将安装程序拷贝到/opt目录下
- 在mysql的安装文件目录下执行:(必须按照顺序执行)
rpm -ivh mysql-community-common-5.7.28-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.28-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.28-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.28-1.el7.x86_64.rpm
如在检查工作时,没有检查mysql依赖环境在安装mysql-community-server会报错
rpm 是Redhat Package Manage缩写,通过RPM的管理,用户可以把源代码包装成以rpm为扩展名的文件形式,易于安装。
-i, --install 安装软件包
-v, --verbose 提供更多的详细信息输出
-h, --hash 软件包安装的时候列出哈希标记 (和 -v 一起使用效果更好),展示进度条

2.3.2. 安装四个安装包
![image-20220803111721000]
/image-20220803111721000.png)
2.3.3. 查看mysql版本
- 执行 mysqladmin --version命令,类似java -version如果打出消息,即为成功
![]()
- 执行rpm -qa|grep -i mysql命令,查看是否安装成功,需要增加 -i 不用去区分大小写,否则搜索不到。

2.3.4. 服务的初始化
为了保证数据库目录为与文件的所有者为 mysql 登录用户,如果你是以 root 身份运行 mysql 服务,需要执行下面的命令初始化:
mysqld --initialize --user=mysql
另外 --initialize 选项默认以“安全”模式来初始化,则会为 root 用户生成一个密码并将该密码标记为过期,登录后你需要设置一个新的密码
查看密码:cat /var/log/mysqld.log

- root@localhost: 后面就是初始化的密码
2.3.5. 启动mysql,并查看状态(加不加.service后缀都可以)
启动:systemctl start mysqld.service
关闭:systemctl stop mysqld.service
重启:systemctl restart mysqld.service
查看状态:systemctl status mysqld.service

- 查看进程:ps -ef | grep -i mysql

2.3.6. 查看mysql服务是否自启动(默认自启动)
systemctl list-unit-files|grep mysqld.service
![]()
- 如不是enabled可以运行如下命令设置自启动
systemctl enable mysqld.service
- 如果希望不进行自启动,运行如下命令设置
systemctl disable mysqld.service

2.3.7. 首次登录
- 通过 mysql -uroot -p进行登录,在Enter password:录入初始化密码

2.3.8. 修改密码
因为初始化密码默认是过期的,所以查看数据库会报错
修改密码:ALTER USER ‘root’@‘localhost’ IDENTIFIED BY ‘new_password’; 但新密码太简单会报错
![]()
- 设置完密码就可以用新密码登录,正常使用数据库了
ALTER USER ‘root’@‘localhost’ IDENTIFIED BY ‘root’;

- 使用quit退出,重新登录

2.3.9. 安装后目录结构
| 参数 | 路径 | 解释 | 备注 |
|---|---|---|---|
| –basedir | /usr/bin | 相关命令目录 | mysqladmin mysqldump等命令 |
| –datadir | /var/lib/mysql/ | mysql数据库文件的存放路径 | |
| –plugin-dir | /usr/lib64/mysql/plugin | mysql插件存放路径 | |
| –log-error | /var/log/mysqld.log | mysql错误日志路径 | |
| –pid-file | /var/run/mysqld/mysqld.pid | 进程pid文件 | |
| –socket | /var/lib/mysql/mysql.sock | 本地连接时用的unix套接字文件 | |
| /usr/share/mysql | 配置文件目录 | mysql脚本及配置文件 | |
| /etc/systemd/system/multi-user.target.wants/mysqld.service | 服务启停相关脚本 | ||
| /etc/my.cnf | mysql配置文件 | cat /etc/my.cnf |
2.3.10. 字符集问题
- 默认latin1字符编码,不支持中文
show variables like ‘%char%’;
- 保存中文报错

- 修改字符集
vim /etc/my.cnf
[client]
default-character-set=utf8
[mysqld]
character-set-server=utf8
collation-server=utf8_general_ci

- 重新启动mysql
systemctl restart mysqld
- 已生成的库表字符集如何变更
- 以前创建的库,创建的表字符集还是latin1

- 修改以前数据库的字符集
alter database mydb character set 'utf8';
- 修改以前数据表的字符集
alter table mytbl convert to character set 'utf8';

但是原有的数据如果是用非’utf8’编码的话,数据本身编码不会发生改变。
已有数据需要导出或删除,然后重新插入。
- 查看字符集
show variables like 'character%';
show variables like '%char%';

3.MySQL架构_用户与权限管理
3.1. MySQL的用户管理
3.1.1. 创建用户
create user zhang3 **identified** by '123123';
- 表示创建名称为zhang3的用户,密码设为123123;
3.1.2. 了解user表
- 查看用户
select * from mysql.user \G; 以列的方式显示数据
select host,user,authentication_string,select_priv,insert_priv,drop_priv from mysql.user;
查询特定字段

- host : 表示连接类型
% 表示所有远程通过 TCP方式的连接
IP 地址 如 (192.168.1.2,127.0.0.1) 通过制定ip地址进行的TCP方式的连接
- 机器名 通过制定网络中的机器名进行的TCP方式的连接
::1 IPv6的本地ip地址,等同于IPv4的 127.0.0.1
localhost 本地方式通过命令行方式的连接 ,比如mysql -u xxx -p xxx 方式的连接。
- user : 表示用户名
同一用户通过不同方式链接的权限是不一样的。
- password : 密码
所有密码串通过 password(明文字符串) 生成的密文字符串。加密算法为MYSQLSHA1,不可逆 。
mysql 5.7 的密码保存到 authentication_string 字段中不再使用password 字段。
- select_priv , insert_priv等
为该用户所拥有的权限。
3.1.3. 设置密码
- 修改当前用户的密码:
set password =password(‘123456’)
修改某个用户的密码:
必须使用root 用户
update mysql.user set authentication_string=password('111111') where user='zhang3';
flush privileges;
#所有通过user表的修改,必须用该命令才能生效。
3.1.4. 删除用户
drop user zhang3 ; 推荐使用!

- 不要通过delete from user where user=‘li4’ 进行删除,系统会有残留信息保留。
3.2. 权限管理
3.2.1. 授权命令
grant 权限1,权限2,…权限n on 数据库名称.表名称 to 用户名@用户地址 identified by ‘连接口令’;
该权限如果发现没有该用户,则会直接新建一个用户。
比如:
grant select,insert,delete,update on atguigudb.* to li4@localhost identified by ‘123’; # 必须有li4 用户才行!
- 给li4用户用本地命令行方式下,授予atguigudb这个库下的所有表的插删改查的权限。
grant all privileges on . to joe@‘%’ identified by ‘123’;
- 授予通过网络方式登录的joe用户 ,对所有库所有表的全部权限,密码设为123.
3.2.2. 收回权限
- 收回权限命令
revoke 权限1,权限2,…权限n on 数据库名称.表名称 from 用户名@用户地址 ;
REVOKE ALL PRIVILEGES ON . FROM joe@‘%’; #收回全库全表的所有权限
REVOKE select,insert,update,delete ON . FROM joe@‘%’;
#收回mysql库下的所有表的插删改查权限
- 须用户重新登录后才能生效
3.2.3. 查看权限
查看当前用户权限:show grants;
查看某用户的全局权限:select * from mysql.user ;
3.3. 通过工具远程访问
grant all privileges on . to root@‘%’ identified by ‘root’;
3.3.1. 先 ping 一下数据库服务器的ip地址确认网络畅通。
# ping 192.168.200.168 -t
3.3.2. 关闭数据库服务的防火墙
systemctl start firewalld.service
systemctl status firewalld.service
systemctl stop firewalld.service
systemctl enable firewalld.service
systemctl disable firewalld.service
3.3.3. 或者不关防火墙,而是,开发3306端口[了解]
查看开放的端口号
firewall-cmd --list-all
设置开放的端口号 生成环境!
firewall-cmd --add-service=http --permanent
firewall-cmd --add-port=80/tcp --permanent
firewall-cmd --add-port=3306/tcp --permanent
重启防火墙
firewall-cmd --reload
3.3.4. 确认MySQL中已经有可以通过远程登录的账户
select * from mysql.user where user='root' and host='%';
3.3.5. 如果没有用户,先执行如下命令
grant all privileges on *.* to root@'%' identified by 'root';
3.3.6. 测试连接

4.MySQL架构_杂项配置sql_mode
4.1. MySQL的sql_mode合理设置
sql_mode设置是否允许一些非法操作,比如允许一些非法数据的插入,数据查询等。
在生产环境必须将这个值设置为严格模式,所以开发、测试、生产环境的数据库也必须要设置,这样在开发测试阶段就可以发现问题。
select @@GLOBAL.sql_mode;全局的sql_mode
select @@SESSION.sql_mode;会话级别sql_mode

4.2. 示例:错误开发演示
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 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
注意: MySQL5.7默认已经设置了ONLY_FULL_GROUP_BY。
4.3. 设置sql_mode
- 设置当前窗口中设置sql_mode
SET GLOBAL sql_mode = ‘modes…’;
SET SESSION sql_mode = ‘modes…’;
- 在/etc/my.cnf中配置sql_mode,永久生效。
[mysqld]
#set the SQL mode to strict
#sql-mode="modes..."
sql-mode = "ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
show variables like ‘sql_mode’;

- sql_mode常用值如下:
set sql_mode=‘ONLY_FULL_GROUP_BY’;
ONLY_FULL_GROUP_BY:对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中
NO_AUTO_VALUE_ON_ZERO:该值影响自增长列的插入。默认设置下,插入0或NULL代表生成下一个自增长值。如果用户希望插入的值为0,而该列又是自增长的,那么去掉该选项。
STRICT_ALL_TABLES,STRICT_TRANS_TABLES
对于支持事务的表,这两种模式是一样的:如果发现某个值缺失或非法,MySQL将抛出错误,语句会停止运行并回滚。
对于不支持事务的表,这两种模式的效果:
如果在插入或修第一个数据行时就发现某个值非法或缺失,那该语句直接抛错,语句停止执行。这个和支持事务的数据表行为时一样的。
如果在插入或修改第n个(n>1)数据行时才发现错误,那就会出现下面的情况:
2.1 在STRICT_ALL_TABLES模式下,停止语句执行,存在部分更新的问题
2.2 在STRICT_TRANS_TABLES模式下,MySQL将继续执行该语句避免“部分更新问题”,对每个非法值将其转换为最接近的合法值。
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:如果需要的存储引擎被禁用或不存在,那么抛出错误。不设置此值时,用默认的存储引擎替代
5. MySQL架构_逻辑架构
5.1. 逻辑架构总体概览
和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。
这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

1.连接层
最上层是一些客户端和连接服务,包含本地socket通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。
主要完成一些类似于连接处理、授权认证、及相关的安全方案。
在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。
同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2.服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。
所有跨存储引擎的功能也在这一层实现,如过程、函数等。
在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化:如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。
如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
2.1 Management Serveices & Utilities: 系统管理和控制工具
2.2 SQL Interface: SQL接口
接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
2.3 Parser: 解析器
- SQL命令传递到解析器的时候会被解析器验证和解析。

2.4 Optimizer: 查询优化器。
- SQL语句在查询之前会使用查询优化器对查询进行优化。
2.5 Cache和Buffer: 查询缓存。
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。
这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
3.引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM****和InnoDB
4.存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
5.2. 利用show profiles 查看sql的执行周期
了解查询语句底层执行的过程:;;;;;;查看是否开启计划。
- 修改配置文件/etc/my.cnf,先开启查询缓存
新增一行:query_cache_type=1
重启mysql:systemctl restart mysqld
- 再开启查询执行计划
show variables like ‘%profiling%’;
set profiling=1;
执行语句两次:select * from mydb.mytbl where id=1 ;
显示最近执行的语句
show profiles;

- 显示执行计划
show profile cpu,block io for query 6;

执行编号7时,比执行编号6时少了很多信息,从下面截图中可以看出查询语句直接从缓存中获取数据;

注意:SQL必须是一致的,否则,不能命中缓存。
如果对数据库表进行 insert ,update ,delete 这个时候,缓存会失效!
如:select * from mydb.mytbl where id=2 和 select * from mydb.mytbl where id>1 and id<3 虽然查询结果一致,但并没有命中缓存。
5.3. 查询说明
mysql的查询流程大致是:
首先,mysql客户端通过协议与mysql服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析,也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储SELECT语句以及相应的查询结果集。
如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。
语法解析器和预处理:首先mysql通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。
mysql解析器将使用mysql语法规则验证和解析查询;
预处理器则根据一些mysql规则进一步检查解析树是否合法。
查询优化器当解析树被认为是合法的了,并且由优化器将其转化成执行计划。
一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
然后,mysql默认使用的BTREE索引,并且一个大致方向是:无论怎么折腾sql,至少在目前来说,mysql只少用到表中的一个索引。
6. 数据结构以及时间复杂度
6.1. 数据结构分类
数据结构有很多种,一般来说,按照数据的逻辑结构对其进行简单的分类,包括线性结构和非线性结构两类。
线性结构:
线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。
线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。
顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
线性结构常见的有:数组、链表、队列和栈。

非线性结构:
非线性结构包括:二维数组,多维数组,树结构,图结构
6.2. 时间复杂度
同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。
http://www.99cankao.com/algebra/log2.php
常见的算法时间复杂度由小到大依次为:O—理解为一个函数



Hash 最好,次之平衡树!
6.3. 算法
算法:解决问题的方案
6.3.1. 算法的时间和空间复杂度
时间复杂度:是指执行算法所需要的基本运算次数。
空间复杂度:是指执行算法所需要的内存空间。
6.4. 空间复杂度
类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元。
在做算法分析时,主要讨论的是时间复杂度。从用户使用体验上看,更看重的程序执行的速度。一些缓存产品(redis, memcache)和算法(基数排序)本质就是用空间换时间。
6.5. 关于时间复杂度得出的结果
加速查找速度的数据结构,常见的有两类:
(1) 哈希,例如HashMap,查询/插入/修改/删除的平均时间复杂度都是O(1);
(2) 树,例如平衡二叉搜索树,查询/插入/修改/删除的平均时间复杂度都是O(log2N);
可以看到,不管是读请求,还是写请求,哈希类型的索引,都要比树型的索引更快一些,
那为什么,索引结构要设计成树型呢?
索引:数据库的索引 -树!
想想范围/排序等其它SQL条件:
哈希型的索引,时间复杂度会退化为O(n)而树型的“有序”特性,依然能够保持O(log2N) 的高效率。
备注:InnoDB并不支持哈希索引。
6.6. 普通二叉树
6.6.1. 检索原理
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
二叉树的特点:
1、一个节点只能有两个子节点,也就是一个节点度不能超过2
2、左子节点 小于 本节点;右子节点大于等于 本节点,比我大的向右,比我小的向左

6.6.2. 问题
这种方式查找:时间复杂度会再次上升
| * 第1种情况 如果id的值是持续递增的话,会是什么样的结构? | ![]() |
|---|---|
| * 第2种情况 | ![]() |
6.7. AVL平衡二叉树
AVL树全称G.M. Adelson-Velsky和E.M. Landis,这是两个人的人名。
在插入数据的时候会自动发生n多次旋转操作,旋转操作会消耗一定的性能!旋转的目的就是保证这个棵数的平衡。平衡二叉树要求左子树与左子树的高度差不能超过1
https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

问题:
在构建二叉树时,需要多次进行i/o操作(海量数据存在数据库或文件中),节点海量,构建二叉树时,速度有影响。
节点海量,也会造成二叉树的高度很大,会降低操作速度。
解决问题:
我们为了减少磁盘IO的次数,就必须降低树的深度,将“瘦高”的树变得“矮胖”。
(1)每个节点存储多个元素
(2)摒弃二叉树结构,采用多叉树
红黑树:
插入顺序:2,1,4,5,3,6

当再次插入7的时候,这棵树就会发生旋转

在这个棵严格的平台树上又进化为“红黑树”{是一个非严格的平台树 左子树与左子树的高度差不能超过1},红黑树的长子树只要不超过短子树的两倍即可!
6.8. BTree索引
6.8.1. 【B树的介绍】
B-Tree树即B树,B即Balanced,平衡的意思。
B-Tree即B树,Balance Tree,平衡树。
2-3树是最简单的B树结构。
B树的阶:节点的最多子节点个数。比如2-3树的阶是3,2-3-4树的阶是4。
B树通过重新组织节点,降低树的高度,并且减少IO读写次数来提升效率。
关键字集合分布在整颗树中,即叶子节点和非叶子节点都存放数据。

https://www.cs.usfca.edu/~galles/visualization/BTree.html
6.8.2. 【检索原理图】
黄色:指针,存储子节点的信息地址
红色:键值,表中记录的主键
蓝色:数据,记录表中除主键外的数据

以上图为例:若查询的数值为5:
第一次磁盘IO:根据根节点找到磁盘块1,读入内存,执行二分查找,比17小,根据指针P1,找左子树;
第二次磁盘IO:找到磁盘块2,读入内存,执行二分查找,比8小,根据指针P1,找左子树;
第三次磁盘IO:找到磁盘块5,读入内存,执行二分查找,找到5,终止。
整个过程中,我们可以看出:BTree相对于平衡二叉树降低了树的高度,缩减了节点个数,减少了I/O操作,提高了查询效率
还可以优化!让每个磁盘块存储更多的指针
6.9. B+Tree索引
6.9.1. 【B+树的介绍】
B+Tree 即B+树:
B+树是B树的升级版本,区别是所有数据只出现在叶子结点中,即叶只有叶子节点存放数据,非叶子节点只是叶子结点中数据的键值和指针。
所有的叶子结点中包含了全部数据信息,及指向含这些数据记录的指针,叶子节点之间通过指针相连,且叶子结点本身依键值的大小自小而大顺序链接。
由于B+树的非叶子节点不存储数据,因此每个节点可以存储更多的信息,假设每个节点能存储4个键值和指针信息,则变成B+树后其结构如下图所示:

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
6.9.2. 【检索原理图】
由于B+树的非叶子节点只存储键值和指针信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+树后其结构如下图所示:

6.9.3. 【BTree和B+Tree比较】
首先,B+树的查找和B树一样,起始于根节点,自顶向下遍历树。
不同的是,B+树中间节点不存储数据,只有键值和指针,而B树每个结点要存储键值和实际数据,这就意味着同样的大小的磁盘块B+树可以容纳更多节点元素,在相同的数据量下,B+树更加“矮胖”,IO操作更少 。
现代操作系统中,磁盘的存储结构使用的是B+树机制,MySQL的InnoDB引擎的存储方式也是B+树机制 。
7. MySQL架构_存储引擎简介
7.1. 查看命令
- 查看mysql提供什么存储引擎:show engines;

命令:show engines \G;
mysql> show engines \G; *************************** 1. row *************************** Engine: InnoDB Support: DEFAULT Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES *************************** 2. row *************************** Engine: MRG_MYISAM Support: YES Comment: Collection of identical MyISAM tables Transactions: NO XA: NO Savepoints: NO *************************** 3. row *************************** Engine: MEMORY Support: YES Comment: Hash based, stored in memory, useful for temporary tables Transactions: NO XA: NO Savepoints: NO *************************** 4. row *************************** Engine: BLACKHOLE Support: YES Comment: /dev/null storage engine (anything you write to it disappears) Transactions: NO XA: NO Savepoints: NO *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO *************************** 6. row *************************** Engine: CSV Support: YES Comment: CSV storage engine Transactions: NO XA: NO Savepoints: NO *************************** 7. row *************************** Engine: ARCHIVE查看默认的存储引擎:show variables like ‘%storage_engine%’;

7.2. 各种引擎介绍
7.2.1. InnoDB存储引擎
o 大于等于5.5之后,默认采用InnoDB引擎。
o InnoDB是MySQL的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)。
o 除了增加和查询外,还需要更新,删除操作,那么,应优选选择InnoDB存储引擎。
o 除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。
7.2.2. MyISAM存储引擎
o MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
o 5.5之前默认的存储引擎
7.2.3. Archive引擎
o Archive档案存储引擎只支持INSERT和SELECT操作,在MySQL5.1之前不支持索引。
o Archive表**适合日志和数据采集(档案)**类应用。
o 根据英文的测试结论来看,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。
7.2.4. Blackhole引擎
o Blackhole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存。
o 但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。
7.2.5. CSV引擎
o CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引。
o CSV引擎可以作为一种数据交换的机制,非常有用。
o CSV存储的数据直接可以在操作系统里,用文本编辑器,或者excel读取。
7.2.6. Memory引擎
o 如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用。
o Memory表至少比MyISAM表要快一个数量级。
7.2.7. Federated引擎
o Federated引擎是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。
7.3. MyISAM和InnoDB
| 对比项 | MyISAM | InnoDB |
|---|---|---|
| 外键 | 不支持 | 支持 |
| 事务 | 不支持 | 支持 |
| 行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作 |
| 缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
| 自带系统表使用 | Y | N |
| 关注点 | 性能:节省资源、消耗少、简单业务 | 事务:并发写、事务、更大资源 |
| 默认安装 | Y | Y |
| 默认使用 | N | Y |
7.4. 阿里巴巴、淘宝用哪个

Percona 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有很显著的提升。
该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
该公司新建了一款存储引擎叫Xtradb完全可以替代Innodb,并且在性能和并发上做得更好,
阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。
AliSql+AliRedisL
8. 索引优化分析_预热_慢&7-Join_作业
8.1. 性能下降SQL慢 执行时间长 等待时间长
数据过多——分库分表 mycat
索引失效,没有充分利用到索引——索引建立
关联查询太多join(设计缺陷或不得已的需求)——SQL优化
服务器调优及各个参数设置(缓冲、线程数等)——调整my.cnf
8.2. 常见通用的Join查询
8.2.1. SQL执行顺序
- 手写

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

- 总结(鱼骨图)

8.2.2. Join图

8.2.3. 建表SQL
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 DEFAULT 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(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_dept_id` (`deptId`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO t_dept(id,deptName,address) VALUES(1,'华山','华山');
INSERT INTO t_dept(id,deptName,address) VALUES(2,'丐帮','洛阳');
INSERT INTO t_dept(id,deptName,address) VALUES(3,'峨眉','峨眉山');
INSERT INTO t_dept(id,deptName,address) VALUES(4,'武当','武当山');
INSERT INTO t_dept(id,deptName,address) VALUES(5,'明教','光明顶');
INSERT INTO t_dept(id,deptName,address) VALUES(6,'少林','少林寺');
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(1,'风清扬',90,1,100001);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(2,'岳不群',50,1,100002);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(3,'令狐冲',24,1,100003);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(4,'洪七公',70,2,100004);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(5,'乔峰',35,2,100005);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(6,'灭绝师太',70,3,100006);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(7,'周芷若',20,3,100007);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(8,'张三丰',100,4,100008);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(9,'张无忌',25,5,100009);
INSERT INTO t_emp(id,NAME,age,deptId,empno) VALUES(10,'韦小宝',18,NULL,100010);
8.2.4. 7种JOIN
- A、B两表共有(查询所有有部门的员工->员工和部门之间必须存在关联的数据)
SELECT FROM t_emp a **INNER JOIN** t_dept b ON a.deptid = b.id ;
2. A、B两表共有+A的独有(列出所有用户,并显示其机构信息)A的全集
SELECT * FROM t_emp a **LEFT JOIN** t_dept b ON a.deptid = b.id ;
3. A、B两表共有+B的独有(列出所有部门,并显示其部门的员工信息 )B的全集
SELECT * FROM t_emp a **RIGHT JOIN** t_dept b ON a.deptid = b.id ;
4. A的独有 (查询没有加入任何部门的员工)
SELECT * FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id WHERE b.id IS NULL ;
5 B的独有(查询没有任何员工的部门)
SELECT * FROM t_emp a RIGHT JOIN t_dept b ON a.deptid = b.id WHERE a.id IS NULL ;
6 AB全有(查询所有员工和所有部门)
#MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
#left join + union(去重)+ right join
SELECT * FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id
UNION
SELECT * FROM t_emp a RIGHT JOIN t_dept b ON a.deptid = b.id ;


SELECT * FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id
UNION ALL
SELECT * FROM t_emp a RIGHT JOIN t_dept b ON a.deptid = b.id ;
1.UNION ALL 不会自动去重。
2.UNION在使用时,两张表的字段保证一致,如果不一致,请在slect后面列选字段,不要使用*
7 A的独有+B的独有(查询没有加入任何部门的员工,以及查询出部门下没有任何员工的部门)
SELECT * FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id WHERE b.id IS NULL
UNION
SELECT * FROM t_emp a RIGHT JOIN t_dept b ON a.deptid = b.id WHERE a.id IS NULL ;

8.2.5. 扩展(掌门人)
- 增加掌门人字段
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;
- 求各个门派对应的掌门人名称
SELECT b.deptname,a.name FROM t_dept b LEFT JOIN t_emp a ON b.ceo = a.id ;
- 求所有当上掌门人的平均年龄:
SELECT AVG(a.age) FROM t_emp a INNER JOIN t_dept b ON a.id = b.ceo ;
求所有人,对应的掌门是谁(课堂练习,4种写法分析)
#临时表连接方式 #step1根据ceo 去查找每个部门的掌门是谁 {SELECT b.id AS deptId,a.name AS ceoname FROM t_emp a INNER JOIN t_dept b ON a.id = b.ceo } #step2 与员工表整合 SELECT c.name,ab.ceoname FROM t_emp c LEFT JOIN ( SELECT b.id AS deptId,a.name AS ceoname FROM t_emp a INNER JOIN t_dept b ON a.id = b.ceo ) ab ON c.deptId = ab.deptId ; #临时表连接方式 根据员工id查找 #step1 根据部门Id 查询员工的信息 SELECT a.name,b.ceo FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id # step2 与员工表整合 SELECT ab.name,c.name AS ceoname FROM ( SELECT a.name,b.ceo FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id ) ab LEFT JOIN t_emp c ON ab.ceo = c.id ; #三表左连接方式 SELECT a.name,c.name AS ceoname FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id LEFT JOIN t_emp c ON b.ceo = c.id ; #子查询方式 SELECT a.name ,(SELECT c.name FROM t_emp c WHERE c.id=b.ceo) AS ceoname FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id ;
8.3. 作业:8个SQL
#1、列出自己的掌门比自己年龄小的人员
#2、列出所有年龄低于自己门派平均年龄的人员
#3、列出至少有2个年龄大于40岁的成员的门派
#4、至少有2位非掌门人成员的门派
#5、列出全部人员,并增加一列备注“是否为掌门”,如果是掌门人显示是,不是掌门人显示否
#6、列出全部门派,并增加一列备注“老鸟or菜鸟”,若门派的平均值年龄>50显示“老鸟”,否则显示“菜鸟”
#7、显示每个门派年龄最大的人
#8、显示每个门派年龄第二大的人
9. 索引优化分析_索引介绍
9.1. 是什么
- MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质:索引是数据结构。你可以简单理解为“排好序的快速查找数据结构”。
(1) 详解(重要)
- 下图就是一种可能的索引方式示例:

左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址
为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
(2) 结论
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
- 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件形式存储磁盘上
磁盘块和磁盘页
- 数据库索引是存储在磁盘上的,如果数据很大,必然导致索引的大小也会很大,超过几个G(好比新华字典字数多必然导致目录厚)
- 当我们利用索引查询时候,是不可能将全部几个G的索引都加载进内存的,为了降低内存开销,InnoDB在把磁盘数据读入到内存时会以页(Page)为基本单位。(一次I/O操作)
- 每一个磁盘块在MySQL中是一个页,页大小是固定的,MySQL InnoDB的默认的页大小是16k,每个索引会分配在页上的数量是由索引字段的大小决定。当索引字段值的长度越长,每一页上的数量就会越少,因此在一定数据量的情况下,索引的深度会越深,影响索引的查找效率。
- SHOW GLOBAL STATUS LIKE ‘Innodb_page_size’; – 16KB

9.2. 优势
类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
9.3. 劣势
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。 因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为更新所带来的键值变化后的索引信息
9.4. MySQL索引
9.4.1. 聚簇索引与非聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。
术语‘聚簇’表示数据跟索引放在一起就是聚簇索引。
如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。

聚簇索引的好处:
按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作。
聚簇索引的限制:
对于MySQL数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。
由于数据物理存储排序方式只能有一种,所以每个MySQL的表只能有一个聚簇索引。一般情况下就是该表的主键。
为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种。
9.5. MySQL索引分类
9.5.1. 单值索引
即一个索引只包含单个列,一个表可以有多个单列索引
语法:
随表一起建索引:
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);
删除索引:
DROP INDEX idx_customer_name on customer;
9.5.2. 唯一索引
索引列的值必须唯一,但允许有空值
语法:
随表一起建索引:
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);
删除索引:
DROP INDEX idx_customer_no on customer ;
9.5.3. 主键索引
设定为主键后数据库会自动建立索引,innodb****为聚簇索引
语法:
随表一起建索引:
CREATE TABLE customer (
id INT(10) UNSIGNED AUTO_INCREMENT ,
customer_no VARCHAR(200),
customer_name VARCHAR(200),
PRIMARY KEY(id)
);
CREATE TABLE customer2 (
id INT(10) UNSIGNED ,
customer_no VARCHAR(200),
customer_name VARCHAR(200)
);
单独建主键索引:
ALTER TABLE customer2 add PRIMARY KEY customer2(id);
删除主键索引:
ALTER TABLE customer2 drop PRIMARY KEY ;
修改主键索引:
必须先删除掉(drop)原索引,再新建(add)索引
9.5.4. 复合索引
即一个索引包含多个列
语法:
随表一起建索引:
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_customer_no_name ON customer(customer_no,customer_name);
删除索引:
DROP INDEX idx_customer_no_name on customer ;
9.5.5. 基本语法
- 创建
CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length));
如果是CHAR,VARCHAR类型,length可以小于字段实际长度; 如果是BLOB和TEXT类型,必须指定length。
ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
- 删除
DROP INDEX [indexName] ON mytable;
- 查看
SHOW INDEX FROM table_name
- 使用ALTER命令
有四种方式来添加数据表的索引:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
9.6. 哪些情况需要创建索引
主键自动建立唯一索引
频繁作为查询条件的字段应该创建索引
查询中与其它表关联的字段,外键关系建立索引
单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组字段
思考:排序和分组哪个更伤性能?
9.7. 哪些情况不要创建索引
- 表记录太少
300万数据时MySQL性能就开始下降了,这时就可以开始开始优化了
- 经常增删改的表
提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
- where条件里用不到的字段不创建索引
10. 索引优化分析_explain查看执行计划
10.1. MySql Query Optimizer
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
当客户端向MySQL 请求一条Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给MySQL Query Optimizer时,MySQL Query Optimizer 首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示Hint信息是否可以完全确定该Query 的执行计划。如果没有 Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
10.2. Explain
10.2.1. 是什么(查看执行计划)
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是 如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
官网介绍 https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

10.2.2. 能干嘛
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
10.2.3. 怎么玩
- Explain + SQL语句
![]()
EXPLAIN输出项(来源于mysql5.7文档)
| Column | JSON Name | Meaning |
|---|---|---|
| id | select_id | The SELECT identifier |
| select_type | None | The SELECT type |
| table | table_name | The table for the output row |
| partitions | partitions | The matching partitions |
| type | access_type | The join type |
| possible_keys | possible_keys | The possible indexes to choose |
| key | key | The index actually chosen |
| key_len | key_length | The length of the chosen key |
| ref | ref | The columns compared to the index |
| rows | rows | Estimate of rows to be examined |
| filtered | filtered | Percentage of rows filtered by table condition |
| Extra | None | Additional information |
10.2.4. 建表语句
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)));
11. 索引优化分析_explain_各字段解释
11.1. id★
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况
1) id相同,执行顺序由上至下
select * from t1,t2,t3;

(2) id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
explain select t1.id from t1 where t1.id = (select t2.id from t2 where t2.id = (select t3.id from t3 where t3.content='t3_897'));

id相同,不同,同时存在;
id如果相同,可以认为是一组,从上往下顺序执行;
在所有组中,id值越大,优先级越高,越先执行;
关注点:id号每个号码,表示一趟独立的查询。一个sql的查询趟数越少越好。
11.2. select_type
- 有哪些

- 查询的类型,主要是用于区别 普通查询、联合查询、子查询等的复杂查询
(1)SIMPLE
- 简单的 select 查询,查询中不包含子查询或者UNION
EXPLAIN SELECT * FROM t1;

(2)PRIMARY
- 查询中若包含任何复杂的子部分,最外层查询则被标记为primary
EXPLAIN select t1.id from t1 where t1.id = (select t2.id from t2 where t2.id = (select t3.id from t3 where t3.content=‘t3_897’));

(3) SUBQUERY
- 在SELECT或WHERE列表中包含了子查询

(4) DEPENDENT SUBQUERY
- 在SELECT或WHERE列表中包含了子查询,子查询基于外层
EXPLAIN SELECT * FROM t3 WHERE id = ( SELECT id FROM t2 WHERE content = t3.content);

(5) UNCACHEABLE SUBQUREY
- 表示这个subquery的查询要受到外部系统变量的影响
EXPLAIN SELECT * FROM t3 WHERE id = ( SELECT id FROM t2 WHERE content = @@character_set_server);

(6) UNION
- 若第二个SELECT出现在UNION之后,则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
EXPLAIN SELECT * FROM (SELECT * FROM t1 UNION SELECT * FROM t2) aa;

(7) UNION RESULT
- 从UNION表获取结果的SELECT

11.3. table
- 显示这一行的数据是关于哪张表的
11.4. partitions
- 代表分区表中的命中情况,非分区表,该项为null
- https://dev.mysql.com/doc/refman/5.7/en/alter-table-partition-operations.html
11.5. type★

- 访问类型排列
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
- 类型介绍
11.5.1. system
- 表仅有一行记录,必须是系统表,这是const类型的特例,查询起来非常迅速。
explain SELECT * from mysql.proxies_priv WHERE User=‘root’;

11.5.2. const
explain select * from t1 where id = 1;

- 表示通过索引一次就找到了,const用于primary key或者unique索引。
- 因为只匹配一行数据,所以很快 如将主键置于where列表中,MySQL就能将该查询转换为一个常量
11.5.3. eq_ref
explain select * from t1,t2 where t1.id = t2.id;

- 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见主键或唯一索引扫描
11.5.4. ref *
create index idx_content on t1(content);
EXPLAIN SELECT * FROM t1, t2 WHERE t1.content = t2.content;

- 非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
11.5.5. range *
explain select * from t2 where id >1 and id <5;

- 只检索给定范围的行,使用一个索引来选择行。
- key 列显示使用了哪个索引 一般就是在你的where语句中出现了between、<、>、in等的查询 这种范围索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
11.5.6. Index
explain select id from t1;

- 出现index是sql使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组
- Full Index Scan,index与ALL区别为index类型只遍历索引树。
- 这通常比ALL快,因为索引文件通常比数据文件小。
- 也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的
11.5.7. all
explain select * from t2;

- Full Table Scan,将遍历全表以找到匹配的行
11.6. possible_keys
- 显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
11.7. key
- 实际使用的索引。如果为NULL,则没有使用索引

11.8. key_len ★
- key_len表示索引使用的字节数,根据这个值可以判断索引的使用情况,特别是在组合索引的时候,判断该索引有多少部分被使用到非常重要。值越大越好。
EXPLAIN SELECT SQL_NO_CACHE * FROM t_emp WHERE t_emp.age=30 AND t_emp.name LIKE ‘ab%’;

- 如何计算
第一组
key_len=age的字节长度+name的字节长度=(4+1) + ( 20*3+2+1) = 5+63 = 68
第二组
key_len=age的字节长度=4+1=5

key_len的长度计算公式:
varchr(10)变长字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
varchr(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)
11.9. ref
显示将哪些列或常量与键列中命名的索引进行比较,以从表中选择行。
explain select * from t1,t2 where t1.id = t2.id;

11.10. rows★
- rows列显示MySQL认为它执行查询时必须检查的行数。值越小越好
11.11. filtered
- 这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数
11.12. Extra★
- 包含不适合在其他列中显示,但十分重要的额外信息
11.12.1. Using filesort *
- 出现filesort的情况:order by 没有用上索引。

- 优化后(给deptno和ename字段建立复合索引),去掉filesort

- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。 MySQL中无法利用索引完成的排序操作称为“文件排序”
11.12.2. Using temporary *
- 出现Using temporary情况:分组没有用上索引。产生临时表。注意:分组操作是需要先排序后分组的。所以,也会出现Using filesort。
- 优化前存在 using temporary 和 using filesort

- 优化后(给deptno和ename建立复合索引)去掉using temporary 和 using filesort,性能发生明显变化:

- 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by 和分组查询group by。
11.12.3. Using index *
表示使用了覆盖索引 [content是一个索引]

- 如果同时出现using where,表明索引被用来执行索引键值的查找;

- 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
11.12.4. Using where
o 表明使用了where过滤!
11.12.5. using join buffer *
如果有它则表明关联字段没有使用索引!

- 使用了连接缓存
11.12.6. impossible where

- where 后面筛选条件有错误!
12. 查询优化_准备数据-保存50万条数据
12.1. 员工表插50W条,部门表插1W条
12.1.1. 建表
CREATE TABLE `dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`ceo` INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
12.1.2. 设置参数:log_bin_trust_function_creators
创建函数,假如报错:This function has none of DETERMINISTIC......
# 由于开启过慢查询日志bin-log, 我们就必须为我们的function指定一个参数。
#主从复制,主机会将写操作记录在bin-log日志中。从机读取bin-log日志,执行语句来同步数据。
#如果使用函数来操作数据,会导致从机和主键操作时间不一致。所以,默认情况下,mysql不开启创建函数设置
#查看mysql是否允许创建函数:
show variables like 'log_bin_trust_function_creators';
#命令开启:允许创建函数设置:
set global log_bin_trust_function_creators=1;
# 不加global只是当前窗口有效。mysqld重启,上述参数又会消失。
#永久方法:
• linux下:/etc/my.cnf下my.cnf[mysqld]加上:log_bin_trust_function_creators=1
12.1.3. 创建函数,保证每条数据都不同
随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
#假如要删除
DELIMITER ;
drop function rand_string;
随机产生部门编号
#用于随机产生多少到多少的编号
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END$$
#假如要删除
DELIMITER ;
drop function rand_num;
12.1.4. 创建存储过程
创建往emp表中插入数据的存储过程
DELIMITER $$
CREATE PROCEDURE insert_emp( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO emp (empno, NAME ,age ,deptid ) VALUES ((START+i),rand_string(6),rand_num(30,50),rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END$$
#删除
DELIMITER ;
drop PROCEDURE insert_emp;
创建往dept表中插入数据的存储过程
#执行存储过程,往dept表添加随机数据
DELIMITER $$
CREATE PROCEDURE `insert_dept`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
#删除
DELIMITER ;
drop PROCEDURE insert_dept;
12.1.5. 调用存储过程
Dept
执行存储过程,往dept表添加1万条数据
CALL insert_dept(10000);
- Emp
执行存储过程,往emp表添加50万条数据
CALL insert_emp(100000,500000);
12.2. 批量删除某个表上的所有索引
12.2.1. 建好的索引存在哪了?
SHOW INDEX FROM t_emp ; 只能查看索引,但不能删除。
Drop index idx_name on table_name;
存储索引的表(元数据库,统计表),我们可以对表数据进行删除操作。
12.2.2. 知识点
- 删除某一个索引
DROP INDEX idx_xxx ON emp
- 查出该表有哪些索引,索引名–>集合
SHOW INDEX FROM t_emp
元数据:meta DATA 描述数据的数据
SELECT index_name FROM information_schema.STATISTICS WHERE table_name=‘t_emp’ AND table_schema=‘mydb’;
AND index_name <>‘PRIMARY’ AND seq_in_index = 1
- 如何循环集合
CURSOR 游标
FETCH xxx INTO xxx
- 如何让mysql执行一个字符串
PREPARE XXX 预编译语句
EXECUTE
- 如何执行存储过程
CALL proc_drop_index (‘mydb’,‘t_emp’);
12.2.3. 创建存储过程
删除表上所有索引
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$$
12.2.4. 执行存储过程
CALL proc_drop_index(“mydb”,“emp”);
CALL proc_drop_index(“mydb”,“dept”);
13. 查询优化_单表使用索引及常见索引失效
13.1. 案例(索引失效)
13.1.1. 全值匹配我最爱
- 系统中经常出现的sql语句如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd';
- 索引应该如何建立 ?
CREATE INDEX idx_age ON emp(age);
CREATE INDEX idx_age_deptid ON emp(age,deptid);
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);
- 建立索引前

- 建立索引后

13.1.2. 最佳左前缀法则
- 如果系统经常出现的sql如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd' ;
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd';
那原来的idx_age_deptid_name 还能否正常使用?不能
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE deptid=4 AND emp.age=30 AND emp.name = ‘abcd’; #

- 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = ‘abcd’ ; # 部分被使用

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = ‘abcd’; # 完全没有

结论:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。
如果where 条件后面索引都存在的情况下,mysql 的优化器会自动使用索引
13.1.3. 计算、函数导致索引失效
- 这两条sql哪种写法更好
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name LIKE ‘abc%’;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3) = ‘abc’;
- 创建索引
CREATE INDEX idx_name ON emp(NAME);
- 第一种:索引优化生效

- 第二种:索引失效


13.1.4. 范围条件右边的列索引失效
- 如果系统经常出现的sql如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = ‘abc’ ;

- 那么索引 idx_age_deptid_name这个索引还能正常使用么?
不能,范围右边的列不能使用。
如果这种sql 出现较多,应该建立:
create index idx_age_name_deptid on emp(age,name,deptid);
- 将范围查询条件放置语句最后:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = ‘abc’ AND emp.deptId>20 ;
- 应用开发中范围查询,例如: 金额查询,日期查询往往都是范围查询。应将查询条件放置where语句最后。
- 效果

13.1.5. 不等于(!= 或者<>)索引失效
CREATE INDEX idx_name ON emp(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> ‘abc’ ;

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name != ‘abc’;
13.1.6. is not null无法使用索引,is null可使用索引
下列哪个sql语句可以用到索引
CREATE INDEX idx_age ON emp(age);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age IS NULL;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age IS NOT NULL;

13.1.7. like以通配符%开头索引失效
- 下列哪个sql语句可以用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE name LIKE ‘ab%’;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE name LIKE ‘**%**ab%’;

13.1.8. 类型转换导致索引失效
- 下列哪个sql语句可以用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE name=123;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE name=‘123’;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ElvECeq1-1660200356803)(https://gitee.com/w0131/java/raw/master/img/202208111438166.jpg)]
name=123发生类型转换,索引失效。
设计实体类属性时,一定要与数据库字段类型相对应,否则会出现类型转换的情况。
13.2. 小总结练习
假设index(a,b,c)
| Where语句 | 索引是否被使用 |
|---|---|
| where a = 3 | Y,使用到a |
| where a = 3 and b = 5 | Y,使用到a,b |
| where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
| where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
| where a = 3 and c = 5 | 使用到a, 但是c不可以,b中间断了 |
| where a = 3 and b > 4 and c = 5 | 使用到a和b, c不能用在范围之后,b断了 |
| where a is null and b is not null | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用 |
| where a <> 3 | 不能使用索引 |
| where abs(a) =3 | 不能使用 索引 |
| where a = 3 and b like ‘kk%’ and c = 4 | Y,使用到a,b,c |
| where a = 3 and b like ‘%kk’ and c = 4 | Y,只用到a |
| where a = 3 and b like ‘%kk%’ and c = 4 | Y,只用到a |
| where a = 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
13.3. 一般性建议
对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
在选择组合索引的时候,过滤性最好的字段在索引字段顺序中,位置越靠前越好。
选择组合索引时,尽量包含where中更多字段的索引
组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面
尽量避免造成索引失效的情况
14. 查询优化_关联查询优化
14.1. 建表SQL
#分类
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)));
14.2. 案例
14.2.1. 采用左连接:left join
下面开始explain分析
EXPLAIN SELECT SQL_NO_CACHE * FROM class LEFT JOIN book ON class.card = book.card;
结论:type 有All
添加索引优化
CREATE INDEX idx_book_card ON book(card);
【右表被驱动表】,可以避免全表扫描

CREATE INDEX idx_class_card ON class(card);
#【左表驱动表】,无法避免全表扫描

14.2.2. 采用内连接:inner join
换成inner join(MySQL自动选择驱动表)
先删除之前建立的索引!class book;
EXPLAIN SELECT SQL_NO_CACHE * FROM class INNER JOIN book ON class.card=book.card;
# 添加索引优化
CREATE INDEX idx_class_card ON class(card);


14.3. 建议
保证被驱动表的join字段被索引
left join 时,选择小表作为驱动表,大表作为被驱动表
inner join 时,mysql会自动将小结果集的表选为驱动表。选择相信mysql优化策略。
能够直接多表关联的尽量直接关联,不用子查询
证明:
explain SELECT a.name ,(SELECT c.name FROM t_emp c WHERE c.id=b.ceo) AS ceoname FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id ;![]() |
|---|
explain SELECT a.name,c.name AS ceoname FROM t_emp a LEFT JOIN t_dept b ON a.deptid = b.id LEFT JOIN t_emp c ON b.ceo = c.id ;![]() |
15. 查询优化_子查询优化
两种:非掌门人员
先创建一个索引 CREATE INDEX idx_ceo ON t_dept (ceo); #①不推荐 Explain SELECT * FROM t_emp a WHERE a.id NOT IN (SELECT b.ceo FROM t_dept b WHERE b.ceo IS NOT NULL); #②推荐 Explain SELECT a.* FROM t_emp a LEFT JOIN t_dept b ON a.id = b.ceo WHERE b.id IS NULL; 结论: NOT IN -->LEFT JOIN xxx ON xx WHERE xx IS NULL 尽量不要使用not in 或者 not exists
16. 查询优化_排序、分组优化
16.1. 排序case
CALL proc_drop_index(“mydb”,“emp”);
CALL proc_drop_index(“mydb”,“dept”);
以下是否能使用到索引,能否去掉using filesort
16.1.1. 总结:无过滤 不索引
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age,deptid;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp ORDER BY age,deptid LIMIT 10;

创建索引
CREATE INDEX idx_age_deptid_name ON emp (age,deptid,NAME);
![]()
增加limit过滤条件,使用上索引了。

16.1.2. 总结:顺序错,必排序
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY deptid;
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY deptid,NAME;
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY deptid,empno;
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY NAME,deptid;
EXPLAIN SELECT * FROM emp WHERE deptid=45 ORDER BY age;
CREATE INDEX idx_age_deptid_name ON emp (age,deptid,NAME)


CREATE INDEX idx_age_deptid_empno ON emp (age,deptid,empno);

![]()

16.1.3. 总结:方向反 必排序
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY deptid DESC, NAME DESC ;
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY deptid ASC, NAME DESC ;


- ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
16.2. 索引的选择
执行案例前先清除emp上的索引,只留主键
CALL proc_drop_index(“mydb”,“emp”);
- 查询 年龄为30岁的,且员工编号小于101000的用户,按用户名称排序
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
开始优化:思路: 尽量让where的过滤条件和排序使用上索引
- 我们建一个三个字段的组合索引可否?
CREATE INDEX idx_age_empno_name ON emp (age,empno,NAME);

我们发现using filesort 依然存在,所以name 并没有用到索引。
原因是,因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了。
所以我们建一个3值索引是没有意义的
那么我们先删掉这个索引:DROP INDEX idx_age_empno_name ON emp;
- 为了去掉filesort我们可以把索引建成
CREATE INDEX idx_age_name ON emp(age,NAME);

也就是说empno 和name这个两个字段我只能二选其一。
这样我们优化掉了 using filesort。性能提升了。

- 如果我们选择那个范围过滤,而放弃排序上的索引呢
DROP INDEX idx_age_name ON emp;
CREATE INDEX idx_age_empno ON emp(age,empno);

果然出现了filesort,而且type还是range光看字面其实并不美好。

结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql,而且快了好多倍。何故?
- 原因
所有的排序都是在条件过滤之后才执行的,所以,如果条件过滤掉大部分数据的话,剩下几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序,但实际提升性能很有限。
相对的 empno<101000 这个条件,如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择。
- 结论:
当【范围条件】和【group by 或者 order by】的字段出现二选一时,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
16.3. 理论:双路排序和单路排序
如果不在索引列上,filesort有两种算法: mysql就要启动双路排序和单路排序
16.3.1. 双路排序(慢)
MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
16.3.2. 单路排序(快)
从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
16.3.3. 结论及引申出的问题
- 但是用单路有问题
在sort_buffer中,单路比多路要多占用很多空间,因为单路是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取sort_buffer容量大小,再排……从而多次I/O。
单路本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
16.3.4. 优化策略
增大sort_buffer_size参数的设置
增大max_length_for_sort_data参数的设置
减少select 后面的查询的字段。 禁止使用select *
16.3.5. 提高Order By的速度
- Order by时select * 是一个大忌。只Query需要的字段, 这点非常重要。在这里的影响是:
当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
- 尝试提高 sort_buffer_size
- 不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程(connection)的 1M-8M之间调整。 MySQL5.7,InnoDB存储引擎默认值是1048576字节,1MB。
SHOW VARIABLES LIKE ‘%sort_buffer_size%’;

- 尝试提高 max_length_for_sort_data
- 提高这个参数, 会增加用改进算法的概率。
SHOW VARIABLES LIKE ‘%max_length_for_sort_data%’; #默认1024字节
- 但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。如果需要返回的列的总长度大于max_length_for_sort_data,使用双路算法,否则使用单路算法。1024-8192字节之间调整
16.4. GROUP BY关键字优化
group by 使用索引的原则几乎跟order by一致 ,唯一区别:
group by 先排序再分组,遵照索引建的最佳左前缀法则
当无法使用索引列,增大max_length_for_sort_data和sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要写在having中了
group by没有过滤条件,也可以用上索引。Order By 必须有过滤条件才能使用上索引。
17. 查询优化_覆盖索引优化
17.1. 什么是覆盖索引?
简单说就是,select 到 from 之间查询的列使用了索引!
select * from tName 不建议使用*
未使用覆盖索引之前:
select sql_no_cache * from emp where name like ‘%abc’;

使用覆盖索引后
create index idx_age_deptid_name on emp(age,deptid,name);
explain select sql_no_cache id,age,deptid from emp where name like ‘%abc’;

17.2. 总结
禁止使用select *
禁止查询与业务无关字段
尽量使用覆盖索引
18. 查询截取分析_慢查询日志
18.1. 是什么
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
18.2. 怎么玩
18.2.1. 说明
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
18.2.2. 查看及开启
- 默认关闭
SHOW VARIABLES LIKE ‘%slow_query_log%’;
默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的,

- 开启:set global slow_query_log=1; 只对窗口生效,重启服务失效

- 慢查询日志记录long_query_time时间
SHOW VARIABLES LIKE ‘%long_query_time%’;
SHOW GLOBAL VARIABLES LIKE ‘long_query_time’;

- 全局变量设置,对所有客户端有效。但,必须是设置后进行登录的客户端。
SET GLOBAL long_query_time=0.1;

- 对当前会话连接立即生效,对其他客户端无效。
SET SESSION long_query_time=0.1; #session可省略

假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,
在mysql源码里是判断大于long_query_time,而非大于等于。
永久生效
- 修改配置文件my.cnf(其它系统变量也是如此)
- [mysqld]下增加或修改参数
- slow_query_log 和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
slow_query_log =1
slow_query_log_file=/var/lib/mysql/localhost-slow.log
long_query_time=3
log_output=FILE
- 关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,如果不设置,系统默认文件:[host_name]-slow.log
18.2.3. case
- 记录慢SQL并后续分析
SELECT * FROM emp;
SELECT * FROM emp WHERE deptid > 1;
- 查询当前系统中有多少条慢查询记录或者直接看慢查询日志
/var/lib/mysql/localhost-slow.log
SHOW GLOBAL STATUS LIKE ‘%Slow_queries%’;

18.3. 日志分析工具mysqldumpslow
在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。
查看mysqldumpslow的帮助信息
a) mysqldumpslow --help

-a: 不将数字抽象成N,字符串抽象成S
-s: 是表示按照何种方式排序;
c: 访问次数 l: 锁定时间 r: 返回记录
t: 查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
-t: 即为返回前面多少条的数据;
-g: 后边搭配一个正则匹配模式,大小写不敏感的;
得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log
得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/localhost-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/localhost-slow.log
另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log | more
19. View视图
19.1. 是什么
将一段查询sql封装为一个虚拟的表。
这个虚拟表只保存了sql逻辑,不会保存任何查询结果。
19.2. 作用
封装复杂sql语句,提高复用性
逻辑放在数据库上面,更新不需要发布程序,面对频繁的需求变更更灵活
19.3. 适用场景
共用查询结果
报表
19.4. 语法
创建
CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition
使用
查询
select * from view_name
更新
CREATE OR REPLACE VIEW view_name
AS SELECT column_name(s) FROM table_name WHERE condition
删除
drop view view_name;
19.5. 案例
CREATE VIEW v_ceo AS
SELECT emp.name, ceo.name AS ceoname
FROM t_emp emp
LEFT JOIN t_dept dept ON emp.deptid = dept.id
LEFT JOIN t_emp ceo ON dept.ceo = ceo.id;
INSERT INTO t_emp (NAME,age,deptId,empno) VALUES ('岳灵珊',18,1,100011);
总结:mysql 视图会随着表中的数据变化而动态变化!
20. 主从复制
20.1. 复制的基本原理
slave会从master读取binlog来进行数据同步
三步骤+原理图

MySQL复制三步骤: 1 master将写操作记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events; 2 slave将master的binary log events拷贝到它的中继日志(relay log); 从服务器I/O线程将主服务器的二进制日志读取过来记录到从服务器本地文件,然后SQL线程会读取relay-log日志的内容并应用到从服务器,从而使从服务器和主服务器的数据保持一致! 3 slave重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化的,而且重启后从接入点开始复制。 * 复制的最大问题(延时)
20.2. 复制的基本原则
每个slave只有一个master
每个slave只能有一个唯一的服务器ID
每个master可以有多个salve
20.3. 一主一从常见配置
20.3.1. 主机配置文件
- 建议mysql版本一致且后台以服务运行

logbin格式:
binlog_format=STATEMENT(默认):数据操作的时间,同步时不一致
binlog_format=ROW:批量数据操作时,效率低
binlog_format=MIXED:是以上两种level的混合使用,有函数用ROW,没函数用STATEMENT,但是无法识别系统变量
server-id=1 log-bin=mysql-bin binlog-ignore-db=mysql binlog-ignore-db=infomation_schema binlog-do-db=mytestdb binlog_format=STATEMENT
先不要创建mytestdb数据库。主从搭建好了再创建。
20.3.2. 从机配置文件
主从所有配置项都配置在[mysqld]节点下,且都是小写字母
[必须]从服务器唯一ID:server-id=2
[可选]启用中继日志:relay-log=mysql-relay

server-id=2
relay-log=mysql-relay
20.3.3. 主从机重启配置生效
systemctl restart mysqld
20.3.4. 主从机都关闭防火墙
- systemctl stop firewalld
20.3.5. 在主机上建立帐户并授权slave
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%' IDENTIFIED BY '123456';
flush privileges;
grant all privileges on . to root@‘%’ identified by ‘root’;
flush privileges;
给root 用户授予远程访问权限
查询master的状态: show master status;

记下File和Position的值
执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化
20.3.6. 在Linux从机上配置需要复制的主机
CHANGE MASTER TO MASTER_HOST=‘主机ip地址’,
MASTER_USER=‘X’,MASTER_PASSWORD=‘X’,
MASTER_LOG_FILE=‘mysql-bin.具体数字’,MASTER_LOG_POS=具体值;

- 启动从服务器复制功能
start slave;

show slave status\G;

下面两个参数都是Yes,则说明主从配置成功!
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
**如果出现:**Slave_IO_Running:No
则可能的解决办法是:
1)停止stop slave; 再启动start slave;看是否能正常运行
2)两个服务器的防火墙是否关闭,是否互相能ping通
3)配置文件是否正确、是否重启了服务器
4)连接主机的语句是否正确
5)可能是uuid 一致(master,slave uuid) vim /var/lib/mysql/auto.cnf 下更改uuid,重启服务
20.3.7. 主机新建库、新建表、insert记录,从机复制
CREATE DATABASE mytestdb;
CREATE TABLE mytbl(id INT,NAME VARCHAR(16));
INSERT INTO mytbl VALUES(1, ‘zhang3’);
INSERT INTO mytbl VALUES(2, ‘li4’);
20.3.8. 如果停止从服务复制功能,重新配置主从
在从机上执行。功能说明:停止I/O 线程和SQL线程的操作。
mysql> stop slave;
在从机上执行。功能说明:用于删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件。
mysql> reset slave;
在主机上执行。功能说明:删除所有的binglog日志文件,并将日志索引文件清空,重新开始所有新的日志文件。
用于第一次进行搭建主从库时,进行主库binlog初始化工作;
mysql> reset master;



