MySQL高级
MySQL架构组成:
高级MySQL介绍:
注意:后面的知识,如果只要了解即可,那么对应的注释你可以不用看
什么是DBA ?
数据库管理员,英文是Database Administrator,简称DBA
百度百科介绍:
一个高级DBA的职责:
负责MySQL的容量规划,架构设计及安装、部署
负责MySQL的日常管理,监控和维护, 并对MySQL进行持续性能优化
负责MySQL开发支持,参与数据架构规划设计,以及相关业务的数据建模、设计评审、SQL代码 审核优化
中级 Java开发工程师对数据库知识的掌握程度:
熟练操作主流数据库,能够通过代码(框架)完成日常的数据库操作
熟练使用SQL,熟悉SQL优化,熟悉存储过程 视图 等创建及使用
了解MySQL的整体体系结构,了解MySQL事务 存储引擎的特点,了解MySQL索引优化,了解MySQL相关锁机制
我们作为Java开发工程师,关注的应该是跟开发相关的数据库知识,了解这些高级的知识
目的是让我们编写出更加高效的应用程序
专业的数据库维护、服务器优化、性能参数调优等等数据库相关的运维工作 还是要交给DBA去做的
MySQL逻辑架构:
学习 MySQL 就好比盖房子,如果想把房子盖的特别高,地基一定要稳,基础一定要牢固
学习 MySQL 数据库前要先了解它的体系结构,这是学好 MySQL 数据库的前提
MySQL架构体系介绍:
MySQL 由连接池、SQL 接口、解析器、优化器、缓存、存储引擎等组成
可以分为四层,即连接层、 服务层、引擎层和文件系统层,后面三层可以统称为服务层
如下是官方文档中 MySQL 的基础架构图:
连接层:
最上面是一些客户端和连接服务,不是MySQL特有的
所有基于网络的C/S的网络应用程序都应该包括 连接处理、认证、安全管理等
服务层:
中间层是MySQL的核心,包括查询解析、分析、优化和缓存等
同时它还提供跨存储引擎的功能,包括 存储过程、触发器和视图等
引擎层:
存储引擎层,它负责存取数据,服务器通过API可以和各种存储引擎进行交互
不同的存储引擎具有不同 的功能,我们可以根据实际需求选择使用对应的存储引擎
存储层:
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互
SQL查询流程:
我们用一条 SQL SELECT 语句的执行轨迹来说明客户端与 MySQL 的交互过程,如下图所示
1:通过客户端/服务器通信协议与 MySQL 建立连接
2:查询缓存,这是 MySQL 的一个可优化查询的地方
如果开启了 Query Cache 且在查询缓存过程中查 询到完全相同的 SQL 语句,则将查询结果直接返回给客户端
如果没有开启Query Cache 或者没有查询到 完全相同的 SQL 语句则会由解析器进行语法语义解析,并生成解析树
3:预处理器生成新的解析树,需要解析树,来使得查询优化
4:查询优化器生成执行计划,查询的优化,自然是选择一个好的操作sql的取值(简称执行计划)
5:查询执行引擎执行 SQL 语句(也就是操作执行计划),此时查询执行引擎会根据 SQL 语句中表的存储引擎类型
以及对应的 API 接口与底层存储引擎缓存或者物理文件的交互情况,得到查询结果
由MySQL Server 过滤后将查询结果缓存并返回给客户端
若开启了 Query Cache,这时也会将SQL 语句和结果完整地保存到 Query Cache 中,以后若有相同的 SQL 语句执行则直接返回结果
即存储引擎,才是真正操作识别sql得到数据的地方,只是这个sql被优化了一个很好的指向(不一定是最好,但也通常是最好)
因为方向很多,可能有差不多的,具体看优化是怎么优化的了
MySQL物理文件:
物理文件包括:日志文件,数据文件,配置文件
日志文件:
日志文件包括:
error log 错误日志,排错,/var/log/mysqld.log(默认开启的)
bin log 二进制日志,备份,增量备份 DDL DML DCL
Relay log 中继日志,复制,接收 replication master
slow log 慢查询日志,调优,查询时间超过指定值
-- 查看错误日志文件路径
SHOW VARIABLES LIKE 'log_error';
-- 慢查询日志文件路径
show variables like 'slow_query_log_file';
-- bin log 日志文件 需要在 my.cnf 中配置
log-bin=/var/log/mysql-bin/bin.log #最好只加上mysql-bin(代表开启)
#否则可能需要很多配置(使得启动不了),我们只需要打开即可
server-id=2 -- 记得唯一(主从之间唯一)
-- 如果不加或者有相同的,那么同步的操作执行不了,比如如果不加,那么执行STOP SLAVE;就会报错
-- 如果相同,那么对应的连接为NO(I/O线程那里)
-- 查看 relay log 相关参数
show variables like '%relay%';
配置文件&数据文件:
配置文件 my.cnf:
在 my.cnf 文件中可以进行一些参数设置,对数据库进行调优
[client]
port = 3307
socket = /data/mysqldata/3307/mysql.sock
default-character-set = utf8mb4
[mysqld]
port = 3307
socket = /data/mysqldata/3307/mysql.sock
pid-file = /data/mysqldata/3307/mysql.pid
basedir = /usr/local/mysql-5.7.11
datadir = /data/mysqldata/3307/data
tmpdir = /data/mysqldata/3307/tmp
character_set_server = utf8mb4
数据文件 :
-- 查看数据文件的位置
show variables like '%dir%';
其中的datadir的值/var/lib/mysql就是数据文件的位置或者目录
其中,里面一般保存了数据库的目录信息,比如我的是如下:
我们可以看到,安装在windows里面的数据库和安装在linux的数据库的对应文件的位置不同,这是因为文件系统的原因
所以通常来说,直接在对方里面安装文件存放是不同的
但是通常来说,只要找到数据文件,那么移动数据文件(数据库名称的目录文件)一般可行,然后刷新即可,即可以移动数据库
因为他们的文件类型是一致的,只是文件存放位置不同而已,比如你将本机的一个数据目录的数据库目录
移动到上面的数据目录里面,然后上面的虚拟机刷新,就可以看到该数据库了,只是可能操作不了
因为文件里面的所属可能不同(地址,版本等等原因)
我们进入一个数据库文件,看如下:
MySQL的备份与恢复:
为什么要进行数据备份:
我们试着想一想,在生产环境中什么最重要?
如果我们服务器的硬件坏了可以维修或者换新,软件问题可以修复或重新安装
但是如果数据没了呢,那么对于一些网站、系统来说,就很有问题了,因为数据库就是一切
所以做好数据 库的备份是至关重要的
数据库备份的应用场景:
数据备份在很多工作中都是经常会用到的,因为数据容易因为各种原因而丢失,造成数据丢失的原因有 哪些呢?
数据丢失应用场景:
系统硬件或软件故障
自然灾害,比如水灾 火灾 地震等
黑客攻击,非法访问者故意破坏
误操作,人为的误操作占比最大
非数据丢失应用场景:
开发测试环境数据库搭建
数据库或者数据迁移
数据备份的类型:
按照业务方式分:
完全备份:
将数据库的全部信息进行备份,包括数据库的数据文件、日志文件
还需要备份文件的存储位置以及数据库中的全部对象和相关信息
差异备份:
备份从最近的完全备份后对数据所做的修改,备份完全备份后变化了的数据文件、日志文件
以及数据库中其他被修改的内容
增量备份:
增量备份是指在一次全备份或上一次增量备份后,以后每次的备份只需备份与前一次相比增加或者被修改的文件
很明显上图中,是每周一进行完全备份,然后周日进行全部备份(周日的备份需要根据备份方式来操作),我们通常也这样操作
备份的组合方式 :
完全备份与差异备份:
以每周数据备份为例,可以在星期一进行完全备份,在星期二至星期六进行差异备份
如果在星期六数据被破坏了,则只需要还原星期一完全的备份和星期五的差异备份
这种策略备份数据需要较多的时间,但还原数据使用较少的时间
完全备份与增量备份:
以每周数据备份为例,在星期一进行完全备份,在星期二至星期六进行增量备份
如果在星期六数 据被破坏了,则需要还原星期一正常的备份和从星期二至星期五的所有增量备份
这种策略备份数据需要较少的时间,但还原数据使用较长的时间
MySQL冷备份和热备份 :
冷备份和热备份指的是,按照数据库的运行状态分类的,当然,他们都可以操作前面的三种备份,只是根据运行状态而言的分开
冷备份:
冷备份指的是当数据库进行备份时,数据库不能进行读写操作,即数据库要下线
冷备份的优点:
是操作比较方便的备份方法(只需拷贝文件)
低度维护,高度安全
冷备份的缺点:
在实施备份的全过程中,数据库必须要作备份而不能作其它工作
若磁盘空间有限,只能拷贝到磁带等其它外部存储设备上,速度比较慢
不能按表或按用户恢复
热备份:
热备份是在数据库运行的情况下,备份数据库操作的sql语句,当数据库发生问题时,可以重新执 行一遍备份的sql语句
热备份的优点:
可在表空间或数据文件级备份,备份时间短
备份时数据库仍可使用
可达到秒级恢复(恢复到某一时间点上)
热备份的缺点:
不能出错,否则后果严重
因难维护,所以要特别仔细小心,不允许"以失败而告终"
实战演练:
冷备份实战:
关闭SELinux:
修改 selinux 配置文件,将SELINUX=enforcing改为SELINUX=disabled,保存后退出
vim /etc/selinux/config
SELINUX=disabled
修改后需要重启使得生效:
reboot
找到MySQL数据文件位置,然后停止MySQL服务
SHOW VARIABLES LIKE '%dir%';
service mysqld stop
service mysqld start
systemctl status mysqld
进入到 /mysql 目录,执行打包命令,将数据文件打包备份:
cd /var/lib/
tar jcvf /root/backup.tar.bz2 mysql/
删除掉数据目录下的所有数据:
rm -rf /var/lib/mysql/
恢复数据 (使用tar命令):
tar jxvf /root/backup.tar.bz2 mysql/
启动MySQL,然后登陆MySQL,查看数据是否丢失,如果数据正常代表冷备成功
service mysqld start
至此,如果有数据,那么就代表冷备份完成,当然,冷备份也可以局部的备份,虽然很麻烦,这里就相当于使用完全备份了
热备份实战:
mysqldump 备份工具 :
mysqldump是MySQL数据库用来备份和数据转移的一个工具,一般在数据量很小的时候(几个G) 可以用于备份
热备可以对多个库进行备份,可以对单张表或者某几张表进行备份
备份单个数据库:
创建文件夹,备份数据
mkdir /databackup
cd /databackup
mysqldump -uroot -p lagou_edu > lagou_edu.sql
模拟数据丢失,删除数据库,然后重新创建一个新的库
DROP DATABASE lagou_edu;
CREATE DATABASE lagou_edu CHARACTER SET 'utf8';
恢复数据:
mysql -uroot -p lagou_edu < lagou_edu.sql
从这里可以得出,他的确也算是备份,只是他的备份与冷备份不同的是,他备份一个语句,而冷备份是备份数据
由于是备份语句,且是在启动中的,那么必然备份时,如果数据变化,可能备份的是以前的,也就是我们说的时间点
那么冷备份可以在启动中操作吗,热备份也可以在不启动中操作吗:
答:冷备份可以,因为他只是改变数据而已,由于启动中数据可能会随时变化,使得备份不知道在哪个状态
但热备份只能在启动中操作,因为是操作语句的,虽然也会操作变化,但因为是语句所以可以很明显的看到变化
虽然冷备份可以使用局部来解决
但总体来说由于冷备份是备份全部(以全部为主,自然不能操作变化的,因为我们需要记住一个个的状态,就如linux的快照一样)
而热备份是备份部分(通常来解决出现问题,可以回到上一个局部状态,因为有总语句存在)
所以我们也认为冷备份只在关闭状态下操作,热备份只在启动状态下操作
但无论是冷备份还是热备份只是我们给与对应备份状态下的操作的别名而已,你甚至可以认为就是备份
备份数据库的某些表:
备份 表数据:
mysqldump -uroot -p lagou_edu course course_lesson > backupTable.sql
模拟数据丢失,删除数据表:
DROP TABLE course;
DROP TABLE course_lesson;
恢复数据:
mysql -uroot -p lagou_edu < backupTable.sql
直接将MySQL数据库压缩备份:
备份数据:
mysqldump -uroot -p lagou_edu | gzip > lagou_edu.sql.gz
模拟数据丢失,删除数据库:
DROP DATABASE lagou_edu;
CREATE DATABASE lagou_edu CHARACTER SET 'utf8';
#在mysql的linux客户端下,通常需要;来结尾,但有些不需要,如use命令
#大概是不同客户端自带的添加吧(这里是linux指定,而我们的图形化界面一般有添加,如果存在自然不会添加)
#但空格基本都是不算(操作)的,除了用特殊符号或者分号(单引号和双引号)的空格,他们的空格会算的(会操作的)
恢复数据:
gunzip < lagou_edu.sql.gz | mysql -uroot -p lagou_edu
至此,我们操作备份完毕
MySQL查询和慢查询日志分析:
SQL性能下降的原因:
在日常的运维过程中,经常会遇到DBA将一些执行效率较低的SQL发过来找开发人员分析
当我们拿 到这个SQL语句之后,在对这些SQL进行分析之前,需要明确可能导致SQL执行性能下降的原因进行分析
执行性能下降可以体现在以下两个方面:
等待时间长:
1:锁表导致查询一直处于等待状态,后续我们从MySQL锁的机制去分析SQL执行的原理
执行时间长:
1:查询语句写的烂
2:索引失效
3:关联查询太多join
4:服务器调优及各个参数的设置
需要遵守的优化原则 :
查询优化是一个复杂的工程
涉及从硬件到参数配置、不同数据库的解析器、优化器实现、SQL 语句 的执行顺序、索引以及统计信息的采集等等方面
下面给大家介绍几个编写SQL的关键原则,可以帮助我们编写出更加高效的 SQL 查询
第一条:只返回需要的结果
1:一定要为查询语句指定 WHERE 条件,过滤掉不需要的数据行
2:避免使用 select * from,因为它表示查询表中的所有字段
第二条:确保查询使用了正确的索引
1:经常出现在 WHERE 条件中的字段建立索引,可以避免全表扫描
2:将 ORDER BY 排序的字段加入到索引中,可以避免额外的排序操作
3:多表连接查询的关联字段建立索引,可以提高连接查询的性能
4:将 GROUP BY 分组操作字段加入到索引中,可以利用索引完成分组
索引就相当于我们操作es时,可以通过索引指向对应的文档,这样我们可以减少要查询的对象
即可以认为mysql的索引与es的类似的效果,因为查询的对象少
但还是不同的,主要的是数据结构的问题,数据结构以后会说明,你可以这样的认为,不同的数据结构,操作的效率不同
特别是查询的,有些可能可以直接的通过条件查询出来的时间,比其他通过条件查询出来的时间短
而mysql的索引就是如此,所以如果一个字段是索引,那么自然更加的容易找到对应的数据
第三条:避免让索引失效
1:在 WHERE 子句中对索引字段进行表达式运算或者使用函数都会导致索引失效
2:使用 LIKE 匹配时,如果通配符出现在左侧无法使用索引
但由于我们通常操作like时,是以全部数据为主,所以通配符基本上都会出现在左边,所以我们也认为,使用like不会操作索引
即如果出现了说明like不会操作索引,一般是认为操作全部数据,即认为通配符基本上都会出现在左边
3:如果 WHERE 条件中的字段上创建了索引,尽量设置为 NOT NULL
因为不是所有的数据库,在判断NULL条件时会使用索引对应的数据,比如IS NULL,设置这个,就直接的避免了IS NULL
对应的IS NOT NULL可能也不会操作,那如果是这个的话,就没有什么办法了,尽量不使用IS NOT NULL即可
SQL的执行顺序:
程序员编写的SQL
MySQL执行的SQL(下面排序的从上到下是执行顺序,即from先执行开始):
上面给出了具体的执行顺序
查看下面的SQL 分析执行顺序:
select
id,
sex,
count(*) AS num
from
employee
where name is not null
group by sex
order by id
上面的SQL执行执行顺序如下:
JOIN查询的七种方式:
7中JOIN,可以分为四类:内连接 、左连接 、右连接、 全连接
JOIN查询SQL编写:
创建表 插入数据:
-- 部门表
DROP TABLE IF EXISTS `t_dept`;
CREATE TABLE `t_dept` (
`id` varchar(40) NOT NULL,
`name` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 员工表
DROP TABLE IF EXISTS `t_emp`;
CREATE TABLE `t_emp` (
`id` varchar(40) NOT NULL,
`name` varchar(40) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`deptid` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `deptid` (`deptid`),
CONSTRAINT `deptid` FOREIGN KEY (`deptid`) REFERENCES `t_dept` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 插入部门数据
INSERT INTO `t_dept` VALUES ('1', '研发部');
INSERT INTO `t_dept` VALUES ('2', '人事部');
INSERT INTO `t_dept` VALUES ('3', '财务部');
-- 插入员工数据
INSERT INTO `t_emp` VALUES ('1', '赵四', 23, '1');
INSERT INTO `t_emp` VALUES ('2', '刘能', 25, '2');
INSERT INTO `t_emp` VALUES ('3', '广坤', 27, '1');
INSERT INTO `t_emp` VALUES ('4', '玉田', 43, NULL);
内连接:
SELECT * FROM t_emp e INNER JOIN t_dept d ON e.deptid = d.id
-- 自然表的先后也影响返回结果,谁先显示的问题
左连接:
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.deptid = d.id
#如果操作了外连接,那么首先以对应的表为主,也就是说,要满足表
#而不是直接的笛卡尔积或者说在笛卡尔积的基础上进行改变(一般是进行改变),即会有顺序
#这里的执行与上面的直接的执行对比就知道了
#下面的解释需要后面对基准和被匹配的描述才可明白:
#简单来说,"这里"(因为他满足这个条件)需要将右边的表的一条数据全部对应好
#才可以下一个,简称满足左表(右表一条数据有多个左边对应)
#所以执行这个,可以看到右表出现两个"研发部"才会下一个(索引的问题)
#换言之就是以左表为基准匹配右表的数据,后面的右连接则是相反的,但说明也是差不多的
#所以这个LEFT代表充分使用左表,即充分满足左边(都进行操作)
#只要操作外连接基本都是如此,或者说,需要以谁为基准的都是如此
左连接去重叠部分 :
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.deptid = d.id WHERE e.deptid IS NULL;
右连接:
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.deptid = d.id
右连接去重叠部分 :
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.deptid = d.id WHERE e.id IS NULL
全连接:
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
#当然,谁的select在前面,就是以谁的结果为基准,所以,这里还是右边是两个研发部,且优先显示上面select的结果
#简单来说,就是后面的select在上面的select的基础上添加新的数据
#注意:UNION使用是有条件的,两边的结果中,字段的数量需要一致,否则执行报错,且显示的字段以前面的字段为主
#自己测试就知道了
MySQL的UNION 操作符用于连接两个以上的 SELECT 语句的结果组合到一个结果集合中
多个 SELECT 语句会删除重复的数据,所以不用担心重复的数据
各自独有:
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.deptid = d.id
WHERE e.deptid IS NULL
UNION
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.deptid = d.id
WHERE e.id IS NULL
实际上我们自己在脑海里进行操作时,是以笛卡尔积来慢慢的操作的
那么有个问题,在全连接时,他是如何操作或者去重相同的一条数据的,答:相当于操作了DISTINCT,并指定所有的字段
即也就导致了如果一条数据完全相同,才会去重,只要有一个字段不同就不会,阈值通常可以进行修改,默认为10秒
很明显UNION就相当于操作使用DISTINCT来指定所有的字段
慢查询日志分析:
慢查询介绍:
MySQL的慢查询,全名是慢查询日志,是MySQL提供的一种日志记录,用来记录在MySQL中响应时间超过阈值的语句
通常用于调优时来查看语句是否是好语句,如果超过阈值,一般执行很慢,即调优失败
默认情况下,MySQL数据库并不启动慢查询日志,需要手动来设置这个参数
如果不是调优需要的话,一般不建议启动该参数
因为开启慢查询日志会或多或少带来一定的性能影响(有操作自然会使用一些资源,也就会使用一些性能)
慢查询日志支持将日志记录写入文件和数据库表
慢查询参数:
执行下面的语句:
SHOW VARIABLES LIKE "%query%";
MySQL 慢查询的相关参数解释:
slow_query_log:是否开启慢查询日志, 1 表示开启, 0 表示关闭,很明显上面就是关闭,因为OFF
slow-query-log-file:新版(5.6及以上版本)MySQL数据库慢查询日志存储路径
long_query_time: 慢查询阈值,当查询时间多于设定的阈值时,记录日志,上面指定的就是10秒
0.001秒=1毫秒,0.000001秒=1微妙,那么很明显10.000000精确到了微妙
慢查询配置方式:
默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的
可以通过设置slow_query_log的值来开启:
set global slow_query_log=1; -- 1代表开启,0代表关闭
SHOW VARIABLES LIKE '%slow_query_log%'; -- 若结果是ON,则代表开启了
SET GLOBAL slow_query_log_file='/var/lib/mysql/bdd'; -- 修改日志文件的位置
-- 注意:在关闭状态下,如果文件的权限不够或者不存在,可能修改不了
-- 并且也启动不了,即报错
-- 当然并不决定,他有时只是指定,可以执行,不会报错,启动后,就自动的创建了,大概是有什么巡查的东西
-- 如果是启动状态下,一般会自动的创建文件,前提是目录需要存在(否则也会报错)
-- 最后,注意:每次的设置,启动后,或者在启动中,都会知道,也就是说,当我们查询文件时,如果出现:
/usr/sbin/mysqld, Version: 5.7.37-log (MySQL Community Server (GPL)). started with:
Tcp port: 3306 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
/usr/sbin/mysqld, Version: 5.7.37-log (MySQL Community Server (GPL)). started with:
Tcp port: 3306 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
/usr/sbin/mysqld, Version: 5.7.37-log (MySQL Community Server (GPL)). started with:
Tcp port: 3306 Unix socket: /var/lib/mysql/mysql.sock
Time Id Command Argument
-- 则代表你肯定设置了三次该文件(心血来潮的执行三次,(●ˇ∀ˇ●)),代表设置的日志记录
使用 set global slow_query_log=1 开启了慢查询日志只对当前数据库(主机,而不是数据库名)生效
且MySQL重启后则会失效(变成OFF),如果要永久生效,就必须修改配置文件my.cnf(其它系统变量基本也是如此)
vim /etc/my.cnf
slow_query_log =1
slow_query_log_file=/var/lib/mysql/lagou-slow.log
service mysqld restart
mysql> SHOW VARIABLES LIKE 'long_query_time';
那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢?
这个是由参数 long_query_time 控制,默认情况下long_query_time的值为10秒
如果超过10秒,那么该sql语句就会记录到慢查询日志里面
show variables like 'long_query_time';
set global long_query_time=1; -- 修改为1秒
show variables like 'long_query_time';
#但是我们发现并没有修改
#实际上是因为使用命令 set global long_query_time=1 修改后,需要重新连接或新开一个会话才能看到修改值
#即他们有会话的固定(该固定针对所有已经正在连接的会话,所以通常需要新连接)
#如果重启mysql,那么不会使用缓存,且默认为10了
#但是也要注意:并不是修改后,立即就会生效(比如可能受网络或者数据库太卡的影响)
#即通常需要等待他后台操作完毕才可,通常来说是很快的,一般来说创建新的会话就会可以查询到
#在这之前,或者说固定没有改变之前,新的连接还是原来的,并且固定好后,就不会变化了(一个会话只能赋值一次固定)
#所以通常需要新连接
#根据上面的说明,通过后面的超时测试,我发现,当前会话固定好的值,才是将sql语句放在日志的主要原因
#也就是说,只要分配了,那么就是操作这个阈值,而不是真实的,所以可能出现
#两个会话,一个会话的语句会记录,另外一个会话的语句不会记录
#因为他们的阈值不同,即与设置的无关,只与分配的固定阈值有关
那么记录到慢查询日志里面是怎么记录的呢:
log_output 参数是指定sql语句日志的存储方式
log_output=‘FILE’ 表示将sql语句日志存入文件(以文件的形式保存,如上面设置的/var/lib/mysql/lagou-slow.log文件里面)
默认值 是’FILE’,log_output=‘TABLE’ 表示将sql语句日志存入数据库,这样日志信息就会被写入到 mysql数据库里的slow_log 表中
SHOW VARIABLES LIKE '%log_output%';
SET GLOBAL log_output = 'file,table' -- 修改存储方式
SET GLOBAL log_output = 'table'
SET GLOBAL log_output = file
-- 其中因为table是真的关键字,所以需要使用引号表示
-- 很明显,我们查询什么,基本上可以直接的使用SET GLOBAL 查询的 = 值,来设置
#当然大多数代表全局的都可以这样
MySQL数据库支持同时两种日志存储方式,配置的时候以逗号隔开即可
如:log_output=‘FILE,TABLE’,这样就可以又以文件来记录sql语句又可以使用数据表来记录sql语句,两个地方都有记录
但日志记录到系统的专用日志表中,要比记录到文件耗费更多的系统资 源
因此对于需要启用慢查询日志,又需要能够获得更高的系统性能的话,那么建议优先记录到文件
系统变量 log-queries-not-using-indexes :未使用索引的查询也被记录到慢查询日志中(可选项),所以说之前的都是操作索引的
设置这个,那么没有使用索引的也会记录到慢查询日志里面了
如果调优的话,建议开启这个选项
show variables like 'log_queries_not_using_indexes';
set global log_queries_not_using_indexes=1; -- 1代表开启(ON),0代表没有开启(OFF),重启,默认为OFF
慢查询测试:
执行 test_index.sql 脚本,他里面有创建数据库,所以直接执行即可(即基本不会出现执行错误),而不用在一个数据库里执行
sql脚本地址:
链接:https://pan.baidu.com/s/1DUWfis9OqIFz82oB4VCbYw
提取码:alsk
执行下面的SQL,执行超时 (超过1秒,前面设置阈值为1秒)我们去查看慢查询日志
SELECT * FROM test_index WHERE hobby = '20009951' OR hobby = '10009931' OR hobby = '30009931';
当然,并不是非要有数据来使得执行多少秒,可以select有个特殊的执行,比如:
SELECT SLEEP(4); -- 等待4秒,当然,返回的数据中,是以SLEEP(4)为字段,且值为0,通常代表用来测试超时
#当然,单纯的直接写字段,比如select a,肯定是不行的(报错)
#除非这样,set @a =1,那么select @a可以查询出来,返回的数据中
#字段是@a(规定当前窗口的用户变量,具体可以到38章博客里查看介绍),且值是1
日志内容:
我们得到慢查询日志后,最重要的一步就是去分析这个日志
我们先来看下慢日志里到底记录了哪些内容
如下图是慢日志里其中一条SQL的记录内容,可以看到有时间戳,用户,查询时长及具体的SQL等
从Time开始后面就是记录,前面的是设置的出现的日志记录
很明显,我们可以看到语句是SELECT SLEEP(1); ,数据库是db4,其他信息就不做说明了
当然,设置阈值的虽然是1秒,但中间可能有其他操作,所以执行该语句或者在计时后(用来确认是否超过阈值)执行该语句
肯定超过了1秒,自然也会记录,通过执行SELECT SLEEP(1),不加分号,对应的语句也是SELECT SLEEP(1);
则代表的确客户端到服务端会默认加上" ; "的(如果不存在就会加,存在自然不加)
接下来我们执行这些来测试:
-- 修改存储方式
-- 经过测试:
SET GLOBAL log_output = 'file,table' -- 文件和表都有添加数据
SET GLOBAL log_output = 'table' -- 只有表添加了数据
SET GLOBAL log_output = file -- 只有文件添加了数据
-- 即的确改变了日志的存储方式
以后,我们可以通过慢查询来查看语句是否满足我们的预期(自己可以设置),太慢了就不好了,即来帮助我们调优
MySQL存储引擎:
存储引擎 介绍:
什么是存储引擎:
存储引擎就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法
就像汽车的发动机一样,存储引擎好坏 决定的数据库提供的功能和性能
存储引擎的作用:
1:并发性
2:事务支持
3:引用完整性
4:索引支持
常见的3种存储引擎:
InnoDB,MyISAM,MEMORY
MySQL也给用户提供了很多种类的存储引擎,主要分两大类:
事务安全表:InnoDB
非事务安全表:MyISAM、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED等
查看MySQL数据的存储引擎有哪些
SHOW ENGINES;
查看当前的默认存储引擎
MySQL5.7 版本或者以上的版本一般默认使用 InnoDB,可能以后的mysql的版本,会使用更好的存储引擎吧
SHOW VARIABLES LIKE '%default_storage_engine%';
在MySQL中,不需要整个服务器都是用同一种引擎,针对具体的需求,可以对每一个表使用不同的存储引擎
并且想要进一步优化,还可以自己编写一个存储引擎
-- 创建新表时指定存储引擎,而不是使用默认的InnoDB(不指定就是他)
create table(...) engine=MyISAM; -- 当然,如果没有对应的存储引擎,自然会执行报错
#且只要你写了ENGINE属性,后面就需要写存储引擎,否则也会报错
我们可以看看,直接的创建表是操作什么存储引擎:
很明显就是InnoDB,即的确是默认的
InnoDB(推荐使用):
InnoDB是一个健壮的事务型存储引擎,这种存储引擎已经被很多互联网公司使用
为用户操作非常大 的数据存储提供了一个强大的解决方案
InnoDB还引入了行级锁定和外键约束,在以下场合下,使用InnoDB是最理想的选择
优点:
Innodb引擎提供了对数据库ACID事务的支持,并且实现了SQL标准的四种隔离级别 支持多版本并发控制的行级锁
由于锁粒度小,写操作和更新操作并发高、速度快,支持自增长列,支持外键,适合于大容量数据库系统,支持自动灾难恢复
缺点:
它没有保存表的行数,那么当我们要获取行数时
比如SELECT COUNT(*) FROM TABLE时需要扫描全表,即在大量的数据下,会需要很多时间
当然,获取的行数sql语句一般只有该方式,如果有其他方式,那么也行,但这个缺点很明显并不重要,即该缺点可以忽略不计
应用场景:
当需要使用数据库事务时,该引擎当然是首选,由于锁的粒度更小,写操作不会锁定全表
所以在并发较高时,使用Innodb引擎会提升效率
且基本能更好的操作更新密集的表(有超多更新操作的表),而InnoDB存储引擎也特别适合处理多重并发的更新请求
MyISAM:
MyISAM引擎,不支持事务、也不支持外键,优势是访问速度快
对事务完整性没有要求或者以select,insert为主的应用基本上可以用这个引擎来创建表
优点:
MyISAM表是独立于操作系统的,这说明可以轻松地将其从Windows服务器移植到Linux服务器
MyISAM存储引擎在查询大量数据时非常迅速,这是它最突出的优点
另外进行大批量插入操作时执行速度也比较快
缺点:
MyISAM表没有提供对数据库事务的支持
不支持行级锁和外键,但支持表级锁(也就是表锁)
不适合用于经常UPDATE(更新)的表,效率低
应用场景:
以读为主的业务,例如:图片信息数据库,博客数据库,商品库等业务
对数据一致性要求不是非常高的业务(不支持事务) 硬件资源比较差的机器可以用 MyiSAM (占用资源少)
MEMORY:
MEMORY的特点是 将表中的数据放在内存中,适用于存储临时数据的临时表和数据仓库中的纬度表
优点:
memory类型的表访问非常的快,因为它的数据是放在内存中的
缺点:
一旦服务关闭,表中的数据就会丢失掉
只支持表锁,并发性能差,不支持TEXT和BLOB列类型,存储varchar时是按照char的方式
应用场景:
目标数据较小,而且被非常频繁地访问
如果数据是临时的,而且要求必须立即可用,那么就可以存放在内存表中
存储在Memory表中的数据如果突然丢失,不会对应用服务产生实质的负面影响
如何选择存储引擎 :
不同的存储引擎都有各自的特点,以适应不同的需求,如表所示
为了做出选择,首先要考虑每一个 存储引擎提供了哪些不同的功能
特性 |
InnoDB |
MyISAM |
MEMORY |
存储限制(Storage limits) |
64TB |
No |
YES |
支持事物(Transactions) |
Yes |
No |
No |
锁机制(Locking granularity) |
行锁,表锁 |
表锁 |
表锁 |
B树索引(B-tree indexes) |
Yes |
Yes |
Yes |
哈希索引(Hash indexes) |
Yes |
No |
Yes |
外键支持(Foreign key support) |
Yes |
No |
No |
存储空间消耗(Storage Cost) |
高 |
低 |
低 |
内存消耗(Memory Cost) |
高 |
低 |
高 |
批量数据写入效率(Bulk insert speed) |
慢 |
快 |
快 |
提供几个选择标准,然后按照标准,选择对应的存储引擎
是否需要支持事务
崩溃恢复,能否接受崩溃
是否需要外键支持
存储的限制
对索引和缓存的支持
这些都比较重要,一般来说,如果这些的总体来说,是好的,那么就选择哪一个
通常我们会使用InnoDB存储引擎,因为他综合来说是比较好的,所以数据库也默认是操作InnoDB存储引擎
那么通过上面的介绍,很明显,sql语句的识别也需要存储引擎的识别,否则也不会操作sql的作用
MySQL索引优化:
索引简介:
什么是索引:
索引就是排好序的,帮助我们进行快速查找的数据结构
简单来讲,索引就是一种将数据库中的记录按照特殊形式存储的数据结构
通过索引,能够显著地提高 数据查询的效率,从而提升服务器的性能
专业一点来说呢,索引是一个排好序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址
在数据库十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用 扫描全表来定位某行的数据
而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据
1:没有用索引时执行 select * from 表名 where,这样的操作,那么数据就是从磁盘一条一条的拿去,最终找到结果, 效率低下
2:为了加快查找,可以维护一个二叉树,左侧节点小于父节点,右侧节点大于父节点
每个节点分别保存字段数据和一个指向对应数据记录物理地址的指针
3:查找时,就可以使用二叉树查找获取相应的数据,从而快速检索出符合条件的记录,比普通的遍历要快
因为遍历必须都操作完,而二叉树可能很快的找到,那么有个问题,如果是多表联查,那么索引是如何操作的呢:
答:"这里是"被匹配的索引会操作,也就是说,如果使用left,那么由于是需要匹配右表的表,所以右表的索引会起作用
也就是说,如果条件是对应的话,查询时,先得到左表的数据,然后将该数据去查询右表的索引的数据结构
即就是之前说的,以左边为基准匹配右表的数据,只是在查询时,即匹配右表时
右表的对应字段数据可能是通过索引查询到的(操作索引的字段),而不是慢慢的遍历过去
当然并不是使用索引就一定会快,比如数据量少的情况
那么因为索引有开销(创建索引,使用索引,一般是使用导致的)或者数据结构的判断开销(执行时间)等等
在这种情况下,可能索引要慢一点
这里就需要考虑很多问题了:
#接下来有很多种情况,主要分为四类,inner left right 直接的where
#其中,直接的where在显示数据中,就是左表为基准匹配右表
#如果是inner,那么与直接的where类似或者说一样
#如果是left,那么只有右表使用索引,左表的索引忽略
#即存在的索引为null,并且使用的索引也为null,前面的两种直接的where和inner可以存在,只是对应的不能使用
#如果是right,那么就只有左表使用索引,右表的索引忽略
#通常我们将使用索引的称为被驱动表,没有使用的称为驱动表
#但也要注意:驱动表是先执行(即EXPLAIN的结果中,在前面),因为需要先得到数据,取匹配索引(被驱动表)
#上面在数据量的情况下,来决定是否使用表的索引,数据量大的使用索引,否则就看显示了
#那么在这个基础上,就会有问题,就算是显示,如果一方没有使用索引,而另一方有使用索引
#那么显示则按照有使用索引的作为被匹配(在后面说明rows属性时,就知道了)
#虽然是这样,但也要看具体操作以及type类型(如后面的rows介绍中的like的操作)
#即可能因为自己的操作使得操作全部(先得到结果导致)
#但大多数是匹配一个
#而没索引的基本都是作为基准,如果都有索引
#那么就是普通的左表为基准匹配右表(仅限于left和right,其他两个则直接的左表为基准匹配右表)
#在索引来对比时,如果是拿索引值,来比对没有索引的,这明显不对
#不符合使用数据结构(因为拿取,自然就是从头拿,而不操作结构)
#所以一般无论什么情况下,是拿没有索引的值,来找索引值
#所以在互相匹配时,其中一方使用了索引,自然可能可以更快的匹配到(实际上需要使用),只是区别是数据结构的不同而已
#被匹配,就是以谁的数据为主,即他需要被其他表都匹配完毕,如前面说过的出现两个的"研发部"
#基准,以谁的数据取匹配被匹配的表,当然也包括笛卡尔积的左表的意思
#最后注意:索引通常不会参与数据的显示,但会参与数据的起始显示,比如主键(主键的自带的排序)
#实际上上面的解释可能并不完善,但总体来说,可能有一定的有效,主要看对应的sql情况而定
#那么是如何得到这样的结论的呢,使用EXPLAIN来查看操作的情况(后面会说明),比如:
我们可以看到,table的d(t_dept表的别名),这个表实际操作了索引(上面的key属性,后面说明EXPLAIN时会讲到))
而e(t_emp表的别名)这个表没有实际操作索引
如果需要知道为什么索引失效的原因,可以到这个地址查看:https://blog.csdn.net/m0_71777195/article/details/125946631
当然,如果不操作别名,那么id需要指定表,否则是执行是失败的(会报错),自然就是没有操作别名的表了
只是没有操作别名而已,需要被指定的存在,如果查询的表没有,那么自然不能直接的写上没有的表,否则也会报错
注意:我的两个表的数据条数相同,即的确是右表操作索引
我们发现,一个表的数据量(条数) 竟然影响了查询的显示结果,所以我们在与后台交互之前时
数据库通常会操作排序,或者有排序字段
使得后台的数据能够更好的获取(直接操作排序,可以通过下标,排序字段一般不可以,如下面的数据显示问题)
如果没有操作排序或者排序字段,后台不要使用下标(因为变化了,这时可能会出现数据的显示问题)
所以最好不要使用下标,而是自己进行排序来操作,或者使用map的key的唯一性来操作等等
当然还有其他方式,但通常数据库操作了排序或者排序字段(一般是排序字段)
所以上面的操作是为了以防万一,这里了解即可
一般来说索引本身也比较大,不可能全部保存在内存中,因此索引通常是以索引文件的形式存储在磁盘上
通常就是在对应的数据文件里面,比如后缀为frm或者ibd的文件里面
索引的种类:
在这之前,给出我的测试操作:
CREATE TABLE tablename(
a INT,
b INT
)
CREATE INDEX a ON tablename (a);
ALTER TABLE tablename ADD INDEX (b);
ALTER TABLE tablename ADD INDEX c (b);
ALTER TABLE tablename ADD INDEX (a);
ALTER TABLE tablename ADD INDEX (b);
CREATE TABLE ccc(
a INT,
INDEX(a)
)
CREATE TABLE cccc(
a INT,
INDEX a(a)
)
CREATE TABLE ccccc(
a INT,
INDEX b(a)
)
CREATE UNIQUE INDEX t ON tablename (a);
ALTER TABLE tablename ADD UNIQUE INDEX af (a);
ALTER TABLE tablename ADD UNIQUE INDEX (a);
CREATE TABLE cccccc(
a INT,
UNIQUE INDEX b(a)
)
CREATE TABLE ccccccc(
a INT,
UNIQUE INDEX (a)
)
CREATE TABLE eee(
a INT,
PRIMARY KEY(a)
)
CREATE TABLE eeee(
a INT,
PRIMARY KEY ab (a)
)
ALTER TABLE tablename ADD PRIMARY KEY (a);
CREATE INDEX vv ON tablename (a,b); -- 多次创建,索引名称相同,会报错
ALTER TABLE tablename ADD INDEX vvv (a,b,b); -- 报错,不能有相同的字段
ALTER TABLE tablename ADD INDEX (b,a); -- 以第一个字段作为有序名称的主体
CREATE TABLE eeeettt(
a INT,
b INT,
INDEX abb (a,b)
)
CREATE UNIQUE INDEX ttt ON tablename (a,b);
ALTER TABLE tablename ADD UNIQUE INDEX (b,a);
ALTER TABLE tablename ADD ff VARCHAR(20);
CREATE FULLTEXT INDEX afg ON tablename (ff); -- 全文索引必须在字符串、文本字段上建立
ALTER TABLE tablename ADD FULLTEXT (ff);
ALTER TABLE tablename ADD FULLTEXT fgd (ff);
CREATE TABLE kskd(
n VARCHAR(20),
FULLTEXT KEY (n)
)
CREATE TABLE kskdd(
n VARCHAR(20),
FULLTEXT KEY v (n)
)
主要的表tablename的信息:
可以根据我的测试操作来看下面的语句格式
普通索引:
这是最基本的索引类型,基于普通字段建立的索引,没有任何限制
CREATE INDEX <索引的名字> ON tablename (字段名);
ALTER TABLE tablename ADD INDEX [索引的名字] (字段名);
CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名) );
-- 上面的[]代表可以写可不行(除了创建表的[]),其他的基本都需要写,否则报错
唯一索引:
与"普通索引"类似,不同的就是:索引字段的值必须唯一,但允许有空值
CREATE UNIQUE INDEX <索引的名字> ON tablename (字段名);
ALTER TABLE tablename ADD UNIQUE INDEX [索引的名字] (字段名);
CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (字段名) );
-- 无非就是多了个UNIQUE
主键索引:
它是一种特殊的唯一索引,不允许有空值,在创建或修改表时追加主键约束即可,每个表只能有一个主键
CREATE TABLE tablename ( [...], PRIMARY KEY (字段名) ); -- 如果是PRIMARY KEY ab (a),名称直接忽略
ALTER TABLE tablename ADD PRIMARY KEY (字段名);
CREATE PRIMARY KEY <索引的名字> ON tablename (字段名); -- 没有这样的情况
复合索引:
用户可以在多个列上建立索引,这种索引叫做组复合索引(组合索引)
复合索引可以代替 多个单一索引,相比多个单一索引,复合索引所需的开销更小
CREATE INDEX <索引的名字> ON tablename (字段名1,字段名2...);
ALTER TABLE tablename ADD INDEX [索引的名字] (字段名1,字段名2...);
CREATE TABLE tablename ( [...], INDEX [索引的名字] (字段名1,字段名2...) );
复合索引使用注意事项:
1:何时使用复合索引,要根据where条件建索引,注意不要过多使用索引,过多使用会对 更新操作效率有很大影响
2:如果索引已经建立了,如建立了(col1,col2),就没有必要再单独建立了,如(col1),虽然可以建立
如果现在有(col1)索 引,且如果查询需要col1和col2条件,可以建立(col1,col2)复合索引或者再次建立(col2)索引
只是复合索引开销小,自然使用复合索引,他们两种对于查询都有一定提 高(一样的)
只是再建立的开销不同而已,使用或者判断都是一样
实际上定义多个,显示的索引(使用的索引也是,这里需要在后面的复合索引的使用方式那里理解)是同一个(最左边)
但既然都是相同索引,反正都是同一个,自然复合索引方便,且开销少
全文索引:
查询操作在数据量比较少时,可以使用like模糊查询,但是对于大量的文本数据检索,效率很低
如果使用全文索引,查询速度会比like快很多倍,在MySQL 5.6 以前的版本
只有MyISAM存储引擎支持全文索引,从MySQL 5.6开始MyISAM和InnoDB存储引擎均支持
CREATE FULLTEXT INDEX <索引的名字> ON tablename (字段名);
ALTER TABLE tablename ADD FULLTEXT [索引的名字] (字段名);
CREATE TABLE tablename ( [...], FULLTEXT KEY [索引的名字] (字段名) ;
全文索引方式有自然语言检索 IN NATURAL LANGUAGE MODE (in natural language mode)
以及布尔检索 IN BOOLEAN MODE(in boolean mode)两种
和常用的like模糊查询不同,全文索引有自己的语法格式,使用 match 和 against 关键字,比如
CREATE TABLE user3(
id INT(11) PRIMARY KEY,
NAME VARCHAR(20),
FULLTEXT KEY full_idx_name(NAME)
)
INSERT INTO user3 VALUES(1,'aabb')
INSERT INTO user3 (12,'aabb') -- 报错,不行字段,也需要写values或者value
INSERT INTO user3 VALUE(12,'aabb') -- values插入单行快(一次性指定多条数据),value插入多行快
INSERT INTO user3 VALUE(122,aabb) -- 报错,不是数字的,需要用单引号或者双引号(即引号)包括
INSERT INTO user3 VALUE(122,"aabb")
SELECT * FROM user3 WHERE MATCH(NAME) AGAINST('aabb'); -- MATCH指定字段,AGAINST指定查询的值
SELECT * FROM user3 WHERE MATCH(NAME) AGAINST('aa');
-- 查询不出值,因为没有aa,那么如何操作模糊查询呢,看如下:
-- * 表示通配符,只能在词的后面
SELECT * FROM user3 WHERE MATCH(NAME) AGAINST('aa*' IN BOOLEAN MODE); -- 代表查询aa开头的所有数据
SELECT * FROM user3 WHERE MATCH(NAME) AGAINST('aa*'IN BOOLEAN MODE); -- 这样也可,即他们本来就是一体的
全文索引使用注意事项:
全文索引必须在字符串、文本字段上建立
全文索引字段值必须在最小字符和最大字符之间的才会有效(innodb:3-84,myisam:4-84)
索引的优势与劣势:
优点:
提高数据检索的效率,降低数据库的IO成本(比如不用浪费很多查询的时间)
通过索引列对数据进行排序(排序的字段是索引),降低数据排序的成本,降低了CPU的消耗
也可以设置索引来使得自动的排序表,比如主键索引
缺点:
创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间,这些空间基本都是磁盘的意思
当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
创建索引的原则:
在经常需要搜索的列上创建索引,可以加快搜索的速度
在作为主键的列上创建索引,强制该列的唯一性和组织表中数据的排列结构
在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度
在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的
在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间
在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度
索引原理:
MySQL中索引的常用数据结构有两种,一种是Hash,另一种是BTree
HASH结构:
Hash底层实现是由Hash表来实现的,是根据键值存储数据的结构
非常适合根据key查找value值,也就是单个key查询,或者说等值查询
对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值
并且不同键值的行计算出来的哈希码也不一样
Hash索引的缺点:
哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行
哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序
哈希索引只支持等值比较查询,不支持任何范围查询和部分索引列匹配查找
即复合索引的列必须都使用,单独使用或者查询一个列,可能不会操作Hash索引
Hsah索引的优点:
只需要做等值比较查询,而不包含排序或范围查询的需求,都适合使用哈希索引
访问哈希索引的数据非常快,除非有很多哈希冲突(冲突的话,那么就要依次匹配了)
B+Tree结构 :
MySQL数据库索引采用的是B+Tree结构,在B-Tree结构上做了优化改造
非叶子节点不存储data数据,只存储索引值(区间),这样便于存储更多的索引值
叶子节点包含了所有的索引值(单个,不是区间)和data数据
叶子节点用指针连接,提高区间的访问性能
B树索引的应用:
全键值查询 where x=123
键值范围查询 where 45 < x < 123
在以后的数据结构的学习中,会具体说明的,这里了解即可
EXPLAIN性能分析:
EXPLAIN简介 :
概述:
使用 EXPLAIN 关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的
分析你的查询语句或是表结构的性能瓶颈,通过explain我们可以获得以下信息:
EXPLAIN的作用:
表的读取顺序(对应id)
数据读取操作的操作类型(对应select_type)
哪些索引可以使用(对应possible_keys) ,即可以说指定的字段是否有索引使用
但并不是必定会操作,如果没有字段有,自然是空的(null)
哪些索引被实际使用(对应key)
实际操作的索引一般会是多个相同对应的索引的第一个,因为总不能操作多个相同的(一般操作第一次创建的)
表直接的引用(对应ref)
每张表有多少行被优化器查询(对应rows)
EXPLAIN的入门:
explain使用:
explain+sql语句,通过执行explain可以获得sql语句执行的相关信息
CREATE TABLE course(
a INT
);
EXPLAIN SELECT * FROM course;
EXPLAIN字段介绍(主要的):
数据准备:
-- 创建数据库
CREATE DATABASE test_explain CHARACTER SET 'utf8';
-- 创建表
CREATE TABLE L1(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L2(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L3(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
CREATE TABLE L4(id INT PRIMARY KEY AUTO_INCREMENT,title VARCHAR(100) );
-- 每张表插入3条数据,即后面的执行三次
INSERT INTO L1(title) VALUES('lagou01'); -- 执行三次
INSERT INTO L2(title) VALUES('lagou02'); -- 执行三次
INSERT INTO L3(title) VALUES('lagou03'); -- 执行三次
INSERT INTO L4(title) VALUES('lagou04'); -- 执行三次
ID介绍:
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
id相同,执行顺序由上至下:
EXPLAIN SELECT * FROM L1,L2,L3 WHERE L1.id=L2.id AND L2.id = L3.id;
EXPLAIN SELECT * FROM L2,L1,L3 WHERE L1.id=L2.id AND L2.id = L3.id;
即的确有顺序,从上到下,但很明显,是id相同时,是从上到下的(根据表的顺序)
那么如果id不同呢,那么表的读取顺序是什么呢:
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
EXPLAIN SELECT * FROM L2 WHERE id = (
SELECT id FROM L1 WHERE id = (SELECT id FROM L3 WHERE title = 'lagou03'));
-- 这里记得修改一下对应的三个表的数据,使得只有一个,否则语句是错误的
-- 操作子查询时,直接讲他看成整体,所以字段之间一般不能互相使用(上面的id只操作自己的,所以不会冲突)
-- 所以一般情况下
-- 子查询不能使用外部查询的字段,可能在有些数据库或者版本中可以使用,但也仅限于当前外部的字段,而不是外部的外部
-- 如果是这样的话,就需要考虑字段重复问题了,通常使用命名解决该问题
-- 这里了解即可
很明显,越是往里面的子查询,通常是先操作的,即L3先执行
实际上根据执行语句的顺序来说,的确需要越子查询的先执行
因为如果是主的先执行,那么后面的就会看成整体了,所以也就没有子查询的操作了,即可能会出现问题
所以mysql使得越子查询的越先执行,来得到数据,到那时,看成整体就不会出现问题了
注意:一般操作相同查询时,如果有表操作了索引
那么后执行,因为需要等待值来匹配他,所以通常操作了索引的条件的表,一般在后面
select_type和table介绍 :
查询类型,主要用于区别普通查询,联合查询,子查询等的复杂查询
simple:简单的select查询,查询中不包含子查询或者UNION
EXPLAIN SELECT * FROM L1;
primary:查询中若包含任何复杂的子部分,最外层查询被标记
EXPLAIN SELECT * FROM L2 WHERE id = (
SELECT id FROM L1 WHERE id = (SELECT id FROM L3 WHERE title = 'lagou03'));
subquery:在select或where列表中包含了子查询
EXPLAIN SELECT * FROM L2 WHERE L2.id = (SELECT id FROM L3 WHERE title = 'lagou03')
可以有多个L3,前提是使用多个子查询,比如:
如果是这样:
EXPLAIN SELECT * FROM L2 WHERE L2.id = 1
就是如下:
derived:在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询(可以说一个子查询有多个查询)
把结果放到临时表中
-- 在测试之前,首先看如下sql
EXPLAIN SELECT * FROM (SELECT * FROM L1 ,L2 ) c
-- 他可以执行吗,答:不可以,因为一个表里面,或者说查询的表,不能有相同的字段(c是结合造成的)
-- 所以我们需要如下:
EXPLAIN SELECT * FROM (SELECT * FROM L1 a UNION SELECT * FROM L2 b) c
-- 注意:使用子查询代表表时,必须加上别名,否则执行报错,因为要使用时,怎么使用呢(因为是新的表)
-- 所以规定需要别名
结果:
即一个子查询,连续操作了多个查询,那么就会将主的字段表(前面说过的以前面的字段为主的表)作为派生的表
那么主查询的表名就是派生表名(而不会显示我们设置的别名),注意:是一个子查询出现多个查询,而不是嵌套的
即不是如下:
EXPLAIN SELECT * FROM L2 WHERE id = (
SELECT id FROM L1 WHERE id = (SELECT id FROM L3 WHERE title = 'lagou03'));
不是这样的,前面有他的查询结果,所以派生实际上通常需要UNION来一起操作(如果有其他方式,也可以使用)
union:如果第二个select出现在UNION之后,则被标记为UNION
如果union包含在from子句 的子查询中,外层select被标记为derived(前面操作过了)
union result:UNION 的结果(即总结果,前面操作的就是对应的主查询,也是UNION的结果)
该结果只是显示,就与查询显示一样,不参与顺序,只代表是合并的显示
比如结果表是:,其中1和2代表id的对应表,即id为1和id为2操作的合并,而不会显示我们设置的别名
EXPLAIN SELECT * FROM L2
UNION
SELECT * FROM L3
很明显那么table字段就是表名(如果是别名,自然显示其别名,而不是主名,主名:没有操作别名之前的表名)
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
-- 即如果是system代表是好的sql,如果是ALL代表是不好的sql(即有很大的优化空间)
-- 即也可以说如果是system,那么基本是执行或者说查询的速度是最快的,而ALL则是最慢的
system:表仅有一行且是全局的使用(等于系统表),这是const连接类型的一个特例,很少出现
const:表示通过索引 一次就找到了,const用于比较 primary key 或者 unique 索引
因为只匹配 一行数据(就算是只有一行也不是system,因为不是全局的使用,可以说,上面的system是规定的,基本不能操作)
所以如果将主键 放在 where条件中,MySQL就能将该查询转换为一个常量
EXPLAIN SELECT * FROM L1 WHERE L1.id = 1
如果不是主键,比如:
CREATE TABLE a(
id INT(11),
title VARCHAR(100),
INDEX (id)
)
INSERT INTO a VALUES(1,1)
EXPLAIN SELECT * FROM a WHERE a.id = 1
正是因为如下,虽然也是索引,但也是可能需要会查询多个的,所以还是可能比const慢,即这里就是ref
实际上索引的数据结构基本相同,只是因为索引本身对表进行了操作
所以也在一定的程度上改变了对应的type值,或者说提高的等级
所以从这里来说,如果你查询的是一个,实际上还是ref,所以对应的type并不代表执行速度,而只是代表整体的一种执行速度
即他整体的执行速度的等级而已,实际上该执行速度的意思是逻辑上的,并不是真的执行速度
或者说,在相同的数据下,一个方式的明显好坏,那么好坏的等级自然就是type的值的等级
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见与主键或唯一索引扫描
很明显,这时候就只会使用该唯一的记录的索引了
EXPLAIN SELECT * FROM L1 ,L2 WHERE L1.id = L2.id ;
-- 注意:这里需要删除一个表的数据,使得只有一条记录对应,比如我们删除L2这个表
-- 也可以这样,所以也更加代表了只是表示整体执行速率的等级,所以只要满足是这个范围里,自然就是对应的等级
EXPLAIN SELECT * FROM L1,L2 WHERE L1.id = L2.title ;
最少只有一条记录(没有记录一般也可),如果大于一条,那么就是ALL了
这里很明显删除的是L2,但对应的值却是L1的改变,因为影响的是代表L1查询的快的等级,所以是L1的type值改变
当然,要出现索引实际使用成功
可能会有多种问题,在前面给过地址,但是,也有可能会有一些小的问题,比如,主键使用varchar类型,那么可能不会使用索引
或者等值查询,两个都操作了索引,可能都不会使用,虽然这些也并不一定,即最好通过EXPLAIN查看即可
ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问
它返回所有匹配 某个单独值的行,这是比较常见连接类型
EXPLAIN SELECT * FROM L1 ,L2 WHERE L1.title = L2.title ;
未加索引之前:
加索引之后:
CREATE INDEX idx_title ON L2(title);
EXPLAIN SELECT * FROM L1 ,L2 WHERE L1.title = L2.title ;
range:只检索给定范围的行,使用一个索引来选择行
EXPLAIN SELECT * FROM L1 WHERE L1.id > 10;
EXPLAIN SELECT * FROM L1 WHERE L1.id IN (1,2);
key显示使用了哪个索引,where 子句后面 使用 between 、< 、> 、in 等查询,这种范围查询要比全表扫描好
因为有范围,自然等级高,但因为找范围要操作整体数据,所以在ref后面:
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
-- system,一个表只有一行数据
-- const,唯一的一个行(通常需要主键,否则可能不会出现,而变成ALL),通过等值找到,由于基本不是规定的,所以不会是system
-- 我们可以发现,system和const的操作基本类似,虽然const需要主键,但是他们都是一个表代表一个行,那么为什么有时候是const有时候是system呢,答:这是因为引擎的原因,如果是myisam引擎,那么一个表一个行,都基本是system,如果是InnoDB引擎,那么一个表一个行,就是以const为主了,基本不会出现system,所以他们两个通常是同样的意思,只是引擎不同而已
-- eq_ref,代表等值查询时,对方的表只有一行或者没有数据
-- ref,添加了索引的操作,但基本没有进行操作,或者很少的操作,正是因为这样,所以这个级别很难达到
-- range,添加了索引,通常操作设置范围的,只是范围需要总体数据,所以比单纯的添加要慢点,即等级比ref低点
-- index,添加了索引,使用他进行分组(通常需要主键,如果是其他字段,那么他只能是除了主键外的唯一的一个字段)
-- 或者与主键索引一起时(只有一个除主键外的其他字段,否则通常认为是ALL,否则不会默认使用,因为*不会操作索引)
-- 且没有使用索引(比如select * from 表名)
-- 那么一般也是这个(特别的一个,通常是操作分组的),否则一般以操作的索引为主
-- 所以自然需要的实际需要的操作,一般比范围range大(具体为什么,看mysql的源码吧)
-- 可以理解为因为判断多或者操作多,所以比range等级低点
-- ALL,基本没有操作索引,也就是说,基本操作所有,即通常是最慢的
-- 上面的等级,在考虑空间的存在下,并不是绝对的越高,优化越好,比如不加索引的const
-- 明明可以直接得到,但因为加了索引,增加了空间,可能在内部还有其他操作需要索引的吧,所以这里是忽略内部的,不需要注意这里的解释
-- 这里也需要格外注意:上面只是用来表达操作方式而已,而并不与执行时间挂钩,只是操作同一个数据时是正确的,即操作相同数据时,上面的越好执行时间就越少,即优化越好
-- 最后注意:无论是主键索引,还是普通索引,还是唯一索引,等等索引,他们都是索引
-- 只是对起始或者表有初始的限制而已,所以主键索引或者唯一索引在操作sql语句时,作用一般与普通索引一样
-- 只是名称不同而已
-- 从这里开始,后面的解释只是具体给出解释,可能并不准确,我们在考虑优化时,基本只需要看这里的值即可
-- 其他的方面并不需要很深入的考虑,因为该值就是最好的考虑
-- 虽然很少情况下,可能该值好,但是还是差点,这只是针对一般理论情况,实际上他可能会提高该值(通常会)
-- 所以总体来说,我们还是以这个值为准
index:出现index 是 SQL 使用了索引,但是没有通过索引进行过滤
所以一般直接的查询表,如果该表存在该索引,也是这个,无论是否指定
但通常一般是使用了索引进行排序分 组
EXPLAIN SELECT * FROM L1 ORDER BY id;
-- 通常需要是主键索引,其他索引需要是单独的
-- 对分组的字段来说
-- 当然,如果表是操作的索引,那么自然是好的结果,操作索引的显示
ALL:对于每个来自于先前的表的行组合,进行完整的表扫描
EXPLAIN SELECT * FROM L1;
一般来说,需要保证查询至少达到range级别,最好能到ref
possible_keys 与 key介绍:
possible_keys:
显示可能应用到这张表上的索引,一个或者多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key:
实际使用的索引,若为null,则没有使用到索引
一般有两种可能没有使用:
1:没建立索引
2:建立索引, 但索引失效,前面有过地址:https://blog.csdn.net/m0_71777195/article/details/125946631
查询中若使用了覆盖索引,则该索引仅出现在key列表中,比如后面的理论上没有使用索引,但实际上使用了的方式
覆盖索引:一个索引包含(或覆盖)所有需要查询的字段的值,通过查询索引就可以获取到字段值
他并不是一个索引,只是这样说明而已,相当于直接的使用:
select * from 表名
-- 变成了
select 表名.字段 from 表名
-- 很明显,只会找该字段了,即相当于覆盖了前面的所有数据,即只显示该字段
-- 我们也将这个称为覆盖索引,可能在其他的地方,有其他的覆盖索引的解释,具体百度即可
-- 即有多个解释,或者多个覆盖索引的说明或直接的是多个覆盖索引
-- 简单来说,指定字段的索引会使用,只是不显示
-- *代表指定所有,但不操作索引(但只是针对除了主键索引外的唯一字段的索引)
-- 当操作指定主键字段时,如果有其他索引,那么不会是使用主键的索引,而是其他索引(即普通索引和唯一索引等等)
-- 说明了不使用主键索引,那么通常是这些索引,但是若只有该主键索引,那么就会使用
-- 那么是如何显示呢,看如下解释:
-- 先后顺序的优先级是:指定优先,整型类型优先,索引先后优先
-- 即满足指定的情况下,那么默认类型若也一样,那么看先后
-- 注意:如果操作的等值(id=1)的值类型,不是自己字段的类型
-- 那么一般也不会使用索引(通常针对于varchar类型,即字符串类型,整型一般不会)
-- 索引的使用通常是会被覆盖的,除非对应的索引失效了,自然就是后来的索引起作用
-- 通常来说,只能使用一个索引,虽然都是索引,但如果同时存在,一般主键索引优先,唯一索引次之,普通索引最后
这里也说明一下复合索引的使用方式:
比如:
CREATE TABLE a(
a INT,
b INT,
c INT,
INDEX (a,b,c)
)
EXPLAIN SELECT * FROM a WHERE c =1
上面的索引,只能是a起作用,所以会出现:
当你使用b索引和c索引时,会被a索引覆盖,且没有对应的索引存在,简称理论上(即是否存在)没有使用索引,但实际上使用了
而这样就不会:
CREATE TABLE c(
a INT,
b INT,
c INT,
INDEX (c)
)
EXPLAIN SELECT * FROM c WHERE c =1
但这样就可以不会覆盖了:
这样c索引就有了,
理论上没有使用索引,但实际上使用了的方式也有如下:
EXPLAIN SELECT L1.id FROM L1;
那么从这里可以看出,possible_keys是识别条件里面的字段的索引,而key则是全局识别并决定是否使用
注意:*也算指定字段,即识别所有的
如果这样:
即,以条件的索引为主,如果没有条件,那么覆盖索引则会操作,有条件,只会操作条件
如果指定的字段有其他字段操作了索引,那么上面的就是ALL
但是要注意:如果操作主键或者唯一索引,那么需要有值
否则基本都是null(从table属性开始,包括table属性,后面基本都是null,除了最后一个Extra属性),索引冲突也基本是这样
索引冲突,双方都使用索引的情况下,相当于没有使用索引
在有值的情况下,则不会考虑字段的索引,则以他们的索引为主(在条件里是这样,但是相同的等值id=id还是会不能使用索引)
这里适用于每个地方
理论和实际上都没有使用索引:
EXPLAIN SELECT * FROM L1 WHERE title = 'lagou01';
理论和实际上都使用了索引:
EXPLAIN SELECT * FROM L2 WHERE id = '1';
或者这样:
key_len介绍:
表示索引中使用的字节数,可以通过该列计算查询中使用索引的长度
key_len 字段能够帮你检查是否充分利用了索引 ken_len 越长,说明索引使用的越充分
创建表:
CREATE TABLE T1(
a INT PRIMARY KEY,
b INT NOT NULL,
c INT DEFAULT NULL,
d CHAR(10) NOT NULL
);
使用explain 进行测试:
EXPLAIN SELECT * FROM T1 WHERE a > 1 AND b = 1;
索引中只包含了1列,所以key_len是4,为什么是4,这里就不做说明了,因为影响的因素实在太多
可以认为一个整型INT就是4(前提是使用了索引,且包括空),因为他就是索引使用后出现的值
比如因素也有这样的,如果在字段后面加上NOT NULL,那么他的值一般会减少1
为b字段添加索引:
ALTER TABLE T1 ADD INDEX idx_b(b);
再次测试:
EXPLAIN SELECT * FROM T1 WHERE a > 1 AND b = 1;
不是操作等值,即索引没有冲突,即都操作了
返回了8,表示两列都使用了索引,所以这里ken_len是8
为d字段添加索引:
ALTER TABLE T1 ADD INDEX idx_d(d);
执行测试:
EXPLAIN SELECT * FROM T1 WHERE d = '';
字符集是utf8,一个字符3个字节,d字段是 char(10)代表的是10个字符,相当30个字节
如果包括空,自然算31,可是有NOT NULL即这里是30了
ref 介绍:
显示索引的哪一列被使用了(即使用谁,或者操作谁,通常是基准)
如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值
L1.id=‘1’,1是常量,ref = const
EXPLAIN SELECT * FROM L1 WHERE L1.id='1';
L2表被关联查询的时候,使用了主键索引
而值使用的是驱动表L1表的ID
执行计划中靠前的表通常是驱动表,通常是"被驱动表"使用索引,这里的"被驱动表"就是L2
除非他有普通索引或者唯一索引,那么会靠前(因为可以指定字段,需要先操作)
所以这里的 ref = lagou_edu.L1.id
即查询(操作)该值
EXPLAIN SELECT * FROM L1 LEFT JOIN L2 ON L1.id = L2.id WHERE L1.title = 'lagou01';
-- 这里修改了对应的L2表的索引名称,即id字段设置的索引名称是id,这里好像是普通索引
rows 介绍:
表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,越少越好
使用like 查询,会产生全表扫描,L2中有3条记录,就需要读取3条记录进行查找
EXPLAIN SELECT * FROM L1,L2 WHERE L1.id = L2.id AND L2.title LIKE '%la%';
-- 前面的%去掉,那么就只使用title索引,但好像没有实际使用,若都去掉,那么通常都会使用,没有造成索引冲突
-- 实际上是因为在and出现后,需要考虑他们整体了,所以局部就没有索引冲突了
-- 而or,则认为局部的冲突也是,即只有满足,那么就是冲突,而不会当成整体
也就是之前说的,以基准匹配被匹配,所以是基准的所有匹配其一个,依次类推
我们也将rows值中的1称为被匹配数,3称为基准或者匹配数
如果使用等值查询,则可以直接找到要查询的记录,返回即可,所以只需要读取一条
EXPLAIN SELECT * FROM L1,L2 WHERE L1.id = L2.id AND L2.title = 'lagou03';
他并没有出现所谓的索引冲突,这是因为and的存在,使得其中一个多出索引时,另外一个会解除冲突,而不会冲突and后面的,即局部没有索引冲突,但是or则会,自己可以进行测试
所以单独的写EXPLAIN SELECT * FROM L1,L2 WHERE L2.id =“1” AND L2.title = ‘lagou03’; ,也会有冲突
总结:
当我们需要优化一个SQL语句的时候,我们需要知道该SQL的执行计划,比如是全表扫描,还是 索引扫描
使用 explain 关键字可以模拟优化器执行 sql 语句,从而知道 mysql 是如何处理 sql 语句 的
方便我们开发人员有针对性的对SQL进行优化
再次的看如下:
表的读取顺序(对应id)
数据读取操作的操作类型(对应select_type)
哪些索引可以使用(对应possible_keys)
哪些索引被实际使用(对应key)
每张表有多少行被优化器查询(对应rows,依次的操作,而不是一次,具体看前面的两个"研发部")
评估sql的质量与效率 (对应type)
extra 介绍:
Extra 是 EXPLAIN 输出中另外一个很重要的列,该列显示MySQL在查询过程中的一些详细信息
准备数据:
CREATE TABLE users (
uid INT PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR(20),
age INT(11)
);
INSERT INTO users VALUES(NULL, 'lisa',10);
INSERT INTO users VALUES(NULL, 'lisa',10);
INSERT INTO users VALUES(NULL, 'rose',11);
INSERT INTO users VALUES(NULL, 'jack', 12);
INSERT INTO users VALUES(NULL, 'sam', 13);
Using filesort:
EXPLAIN SELECT * FROM users ORDER BY age;
执行结果Extra为 Using filesort ,这说明,得到所需结果集,需要对所有记录进行文件排序
这类SQL语句性能极差,需要进行优化
典型的,在一个没有建立索引的列上进行了order by或者说,该表是没有操作索引的,就会触发filesort,常见的优化方案是
在order by的列上添加索引,避免每次查询都全量排序
filtered 它指返回结果的行占需要读到的行(rows列的值)的百分比
Using temporary:
ALTER TABLE users ADD sex CHAR(1);
EXPLAIN SELECT COUNT(*),sex FROM users WHERE uid > 2 GROUP BY sex;
执行结果Extra为 Using temporary(有使用索引,是因为对应的表是操作了索引的)
这说明需要建立临时表 (temporary table) 来暂存中间结果
常见与 group by 和 order by,这类SQL语句性能较低,往往也需要进行优化
Using where:
EXPLAIN SELECT * FROM users WHERE age=10;
语句的执行结果Extra为Using where,表示使用了where条件过滤数据
需要注意的是:
返回所有记录的SQL,不使用where条件过滤数据,大概率不符合预期,对于这类SQL往往需 要进行优化
使用了where条件的SQL,并不代表不需要优化,往往需要配合explain结果中的type(连接 类型)来综合判断
例如本例查询的 age 未设置索引,所以返回的type为ALL,仍有优化空 间,可以建立索引优化查询
Using index:
ALTER TABLE users ADD INDEX(uname);
EXPLAIN SELECT uid,uname FROM users WHERE uname='lisa';
单个,那么一般row就是代表找到的条数了,而不是匹配数或者被匹配数(前面有说明过)
此句执行结果为Extra为Using index,说明sql所需要返回的所有列数据均在一棵索引树上,而无需访问实际的行记录
因为主键字段使用的是uname所以是同一个(指定相同的自然只会使用一个索引)
Using join buffer:
EXPLAIN SELECT * FROM users u1 LEFT JOIN (SELECT * FROM users WHERE sex = '0') u2 ON u1.uname = u2.uname;
执行结果Extra为 Using join buffer (Block Nested Loop)
说明,需要进行嵌套循环计算,这里每个表都有五条记录,内外表查询的type都为ALL
问题在于 两个关联表join 使用 uname,关联字段均未建立索引,就会出现这种情况
常见的优化方案是,在关联字段上添加索引,避免每次嵌套循环计算
MySQL锁机制:
MySQL锁概述:
锁的概念 :
数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则
假设当前商品只有一件,两个用户同时购买,我们需要保证只有一个用户能下单成功
因为购买行为是一组操作,这里需要使用事务控制,从获取商品数量,插入订单,到付款后插入付款信息
更新商品数量,在这个过程中,使用锁可以对有限的资源进行保护
MySQL的锁分类 :
MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,MySQL中不同的存储引擎支持不同的锁机制
MyISAM和MEMORY存储引擎采用的表级锁
InnoDB存储引擎既支持行级锁,也支持表级锁,默认情况下采用行级锁
BDB采用的是页面锁,也支持表级锁
按照数据操作的类型分:
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响,但一般不能操作写操作,只能读
写锁(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁
按照数据操作的粒度分:
表级锁:开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低
行级锁:开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高
页面锁:开销和加锁时间界于表锁和行锁之间,会出现死锁,锁定粒度界于表锁和行锁之间,并发度一般
按照操作性能可分为乐观锁和悲观锁:
乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测
如果发现冲突了,则提示错误信息
悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改的控制方式
共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴
表级锁(偏读,因为基本读的操作多):
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁
它实现简单,资源消耗较少,被大部分MySQL引擎支持
最常使用的MYISAM与INNODB都支持表级锁定
表级锁定分为:表共享读锁(共享锁)与表独占写锁(排他锁)
特点:开销小,加锁快,不会出现死锁,锁定粒度大,发出锁冲突的概率最高,并发度最低
数据准备:
-- 创建数据库
CREATE DATABASE test_lock CHARACTER SET 'utf8';
USE test_lock;
-- 创建表,选择 MYISAM存储引擎
CREATE TABLE mylock01(
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(20)
)ENGINE MYISAM;
-- 创建表
CREATE TABLE mylock02(
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(20)
)ENGINE MYISAM;
-- mylock01表中向插入数据
INSERT INTO mylock01(title) VALUES('a1');
INSERT INTO mylock01(title) VALUES('b1');
INSERT INTO mylock01(title) VALUES('c1');
INSERT INTO mylock01(title) VALUES('d1');
INSERT INTO mylock01(title) VALUES('e1');
-- mylock02表中向插入数据
INSERT INTO mylock02(title) VALUES('a');
INSERT INTO mylock02(title) VALUES('b');
INSERT INTO mylock02(title) VALUES('c');
INSERT INTO mylock02(title) VALUES('d');
INSERT INTO mylock02(title) VALUES('e');
SELECT * FROM mylock01;
加锁语法:
查看表中加过的锁:
-- 0表示没有加锁,当前的所有数据库表都没有加锁
SHOW OPEN TABLES;
-- 查询加锁的表,条件In_use 大于0
SHOW OPEN TABLES WHERE In_use > 0;
手动增加表锁:
-- 语法格式:LOCK TABLE 表名 READ(WRITE),表名2 READ(WRITE), (以此类推);
-- 为mylock01加读锁(共享锁),给mylock02加写锁(排他锁)
lock table mylock01 read,mylock02 write; -- 会开启两个锁的进程,不能是这样
-- LOCK TABLE mylock01 READ,mylock01 WRITE; ,即一次性只能给一个表加一个锁,否则报错
SHOW OPEN TABLES WHERE In_use > 0;
释放锁,解除锁定:
-- 方式1
unlock tables; -- 释放当前数据库的所有表的所有锁(自然包括只读和只写,我们也将他们称为共享锁和排他锁,即只读为共享锁的简称,只写为排他锁的简称,当然也包括锁的类型,比如表锁和行锁等等也会释放)
-- 但是锁进程还在
-- 方式2 找到锁进程,得到id
SHOW PROCESSLIST;
kill id -- 直接删除进程,自然对应的锁也会释放,虽然可能报错(错误代码),但实际上还是删除的,所以报错不用管
-- 注意:一般也会删除当前数据库的设置,比如SET autocommit=0;,使得变成默认的,一般默认为ON即默认提交
-- 无论你怎么删除,当前的数据库总会出现(或变成)对应的id所指定的
-- 虽然是会话变量,但实际上会话变量也是操作数据库,所以自然也会重置,所以一般会话变量是存在数据库的变量
kill 34,那么也就释放锁了
即实际上锁的操作也就是sql语句的操作,只是底层实现了锁而已,但我们只需要知道sql语句即可
上面的Query代表当前所在的数据库
加读锁测试:
MySQL 的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
对mylock01表加读锁:
lock table mylock01 read;
开启两个窗口,对mylock01进行读操作,两个窗口都可以读
select * from mylock01;
在其中一个窗口进行写操作 (update) 失败
update mylock01 set title='a123' where id = 1; -- 一般会直接的卡住,可能也会出现报错
-- 当然,出现卡住,是因为其他的会话操作的锁,或者说是进程的锁,为什么这样说呢,看如下解释:
-- 如果当前会话设置了只读,那么无论其他会话是否设置,当你操作这个更新时,会直接的报错
-- 如果其他会话设置了只读,那么这里就会一直等待(可能等待到一定的实际会出现错误,但一般不会,通常是一直等待)
-- 也就是说,实际上不同的会话,在操作锁时,是不同的锁进程的,他们的锁
-- 对其他会话来说是阻塞(等待)的操作,对当前会话来说是判断(报错)的操作
-- 简单来说,只有当所有的锁没有了,那么才会真正的没有锁
-- 注意:如果在阻塞中,突然的解除锁,那么就会执行,即它里面是个类似于循环的操作,所以会执行
-- 当然,如果你的sql有报错的问题,那么执行也是会报错的
-- 所以他只是阻塞你底层中一开始提交sql的第一步,或者说,关键的一步(因为后面就是真正的执行了)
总结:
对MyISAM表的读操作 (加读锁),不会阻塞其他进程对同一表的读请求
但是会阻塞对同一表的写请求,只有当读锁释放后,才会执行其他进程的写操作
加写锁测试:
在窗口1中对mylock01表加写锁
lock table mylock01 write;
在窗口1中,对 mylock01 进行读写操作,都是可以进行的
select * from mylock01 where id = 1;
update mylock01 set title = 'a123' where id = 1;
-- 注意:他因为给该表加上写锁,那么当前的会话,只能操作该数据库的该表,否则报错
-- 但是其他会话不可以操作该数据库的该表
-- 加锁也不行,而正是因为这样,所以自己基本不会有,即只有一个写锁
-- 但读锁却可以有多个,因为读锁可以加读锁,虽然不能加写锁
-- 基本上只要是关于该表的都不能操作,阻塞的形式,所以释放就会执行
-- 但可以操作其他数据库的其他表,只要不是该表就行
-- 那么如果只读和只写都操作了,那么相当于他们的结合,具体结合看如下解释:
-- 只写:当前会话只能读取和写入该表,其他会话,不能读取和不能写入该表(也包括了不能加写锁和不能加读锁,即都不可以加,又因为读锁可以加读锁,但不能加写锁,所以写锁是单独锁,但读锁可以是多个,即多数锁)
-- 或者说,除了查询,基本都是写入
-- 只读:所有的会话,都可以读取,只是,所有的会话都不能写入
-- 我们发现如果只读和只写在一起,实际上只读会覆盖只写的不能读取以及可以写入的这个情况
-- 所以其他会话可以读取了,但还是不能写入,且当前会话不能写入了,但有个前提,需要只读后启动,因为需要覆盖
-- 如果只写后启动,那么就以写入为主
-- 即有如下:
-- 如果是读取后启动,那么可以认为,当前会话只能读取且不能写入该表,其他会话,能读取但不能写入该表
-- 多个只能(是写入的,这里没有被只读覆盖)
-- 如果是写入后启动,那么可以认为,当前会话只能读取和写入该表,其他会话
-- 不能读取且不能写入该表(这里反正一样,所以是否覆盖都是一样,所以只写后启动,相当于只读并没有启动一样)
-- 至此通过上面,可以得出结论,因为其他的基本相同,只是只写有一个"只能"
-- 所以他们一起时,只是代表"只能"的其他的是只写的操作还是只读操作而已
-- 对一个会话里加读锁或者写锁,都只是一个进程,实际上In_use代表该所有表进程里面没有释放锁的数量
-- 而不是只读和只写的数量,所以如果是1,那么该1可能有只读和只写
总结:对MyISAM表加写锁,会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的操作
行级锁(偏写,因为基本写的操作多)
行级锁介绍:
行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大
但是也会造成死锁,每次加锁和释放锁的开销也会变大
使用MySQL行级锁的两个前提
使用 innoDB 引擎
开启事务 (隔离级别为 Repeatable Read ,即可重复读的作用)
InnoDB行锁的类型:
共享锁(S):当事务对数据加上共享锁后,其他用户可以并发读取数据
但他们的事务都不能对数据进行修改(即不能操作添加排他锁。因为修改操作一般是默认添加排他锁的,除非不是,那么就可以修改),直到已释放所有共享锁
排他锁(X):如果事务T对数据A加上排他锁后,则其他事务不能再对数据A加任任何类型的封锁
当前事务既能读数据,又能修改数据,但其他事务不能,除了普通查询
很明显,他们说的其他,即无论是共享锁还是排他锁
这里的两个锁,自己如何操作,并不会影响(覆盖的),但共享还是不能覆盖排他的特殊,即对方还是阻塞(特殊的)
反过来可以使得不阻塞
他们的意思基本相同,只是有冲突问题而已,也很明显与表的共享锁和排他锁是不同的(有点不同)
之所以是有点,因为在冲突这里,与表的锁类似,但是加了排他锁,但还是可以访问其他表,或者其他字段
因为这是行的共享锁和排他锁
加锁的方式 :
InnoDB引擎默认更新(写)语句,update,delete,insert 都会自动给涉及到的数据加上排他锁
select语句默认不会加任何锁类型,如果要加可以使用下面的方式:
-- 加共享锁(S):
select * from table_name where ... lock in share mode;
-- 加排他锁(x):
select * from table_name where ... for update;
-- 针对自己
锁兼容:
共享锁只能兼容共享锁,不兼容排它锁
排它锁互斥共享锁和其它排它锁
也就是前面说的可以操作多个只读,但只能操作一个只写
行级锁都是基于索引的,如果一条 SQL 语句用不到索引是不会使用行级锁的
而会使用表级锁把整张表锁住,这点需要咱们格外的注意,虽然看起来并没有什么不同
行锁测试:
更新时的行锁测试:
数据准备:
#创建表
CREATE TABLE innodb_lock(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
INDEX idx_name(NAME)
);
-- 插入数据
INSERT INTO innodb_lock VALUES(NULL,'a', 13);
INSERT INTO innodb_lock VALUES(NULL,'b', 23);
INSERT INTO innodb_lock VALUES(NULL,'c', 33);
INSERT INTO innodb_lock VALUES(NULL,'d', 43);
打开两个窗口,都开启手动提交事务 (提交事务或回滚事务就会释放锁)
#开启MySQL数据库手动提交
SET autocommit=0;
-- 查看状态,设置为0则是OFF,设置为1则是ON
SHOW VARIABLES LIKE 'autocommit';
-- 如果再变成1,那么操作后就会自动提交
执行不同会话修改操作,窗口1读,窗口2 写
窗口1 进行,对id为1的数据进行更新操作,但是不进行commit
执行之后,在当前窗口查看表数据,发现被修改了
update innodb_lock set name = 'aaa' where id=1;
select * from innodb_lock;
在窗口2 查看表信息,无法看到更新的内容:
select * from innodb_lock;
总结:行级锁中的写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作
其实就算读取了也不会给操作后的数据(普通查询)
从而可以有效避免"脏读"问题的产生,即实际上隔离级别就已经使用了锁了
窗口1 对innodb_lock表的 id=1 的这一行,进行写操作,但是不要commit
update innodb_lock set name = 'abc' where id=1;
接下来 窗口2 也对innodb_lock表的 id=1 的这一行,进行写操作,发现发生了阻塞
update innodb_lock set name = 'a123' where id=1;
等窗口1执行commit语句之后,窗口2的SQL就会执行了
总结:在有写锁(排他锁)的情况下,其他事务不能再对当前数据添加写锁,从而保证数据的一致性
从而避免了不可重复读的问题,其他字段也是一样,因为操作表,自然会操作行,即会阻塞(写锁的原因)
而正是因为这样,所以mysql也默认这个可重复读,在36章博客有具体说明
查询时的排他锁测试:
select语句加排他锁方式:select * from table_name where … for update
for update 的作用:
for update 是在数据库中上锁用的,可以为数据库中的行上一个排他锁
for update 的应用场景:
存在高并发并且对于数据的准确性很有要求的场景,是需要了解和使用for update的
for update 的注意点:
for update 仅适用于InnoDB,并且必须开启事务,在begin与commit之间才生效
当然如果设置了不自动提交也行,因为他们执行时,一般也是默认开启事务的
在窗口1中, 首先开启事务,然后对 id为1 的数据进行排他查询
select * from innodb_lock where id = 1 for update;
-- 如果设置了不自动提交,那么也认为是在开启事务的,没提交,虽然查询并没有说是自动提交的
-- 但实际上也可以认为,也是事务操作自动提交
在窗口2中,对同一数据分别使用 排他锁 和 共享锁 两种方式查询
-- 排他锁查询
select * from innodb_lock where id = 1 for update;
-- 共享锁查询
select * from innodb_lock where id = 1 lock in share mode;
我们看到开了排他锁查询和共享锁查询都会处于阻塞状态,因为id=1的数据已经被加上了排他 锁
此处阻塞是等待排他锁释放
如果只是使用普通查询,我们发现是可以的
select * from innodb_lock where id = 1;
-- 也就是说,对应的锁不针对普通查询,本质上只是没有锁的冲突而已,因为没有使用锁
-- 由此可知,实际上他们只是操作锁的冲突,所以,只要你的锁没有冲突,自然可以查询
-- 即而前面说的行锁的共享和排他,都只是对锁来实现了
查询时的共享锁测试:
添加共享锁:select * from table_name where … lock in share mode
事务获取了共享锁,在其他查询中也只能加共享锁,但是不能加排它锁
窗口1 开启事务,使用共享锁查询 id = 2 的数据,但是不要提交事务
select * from innodb_lock where id = 2 lock in share mode;
窗口2 开启事务,使用普通查询和共享锁查询 id = 2 的数据,是可以的
select * from innodb_lock where id = 2 lock in share mode;
select * from innodb_lock where id = 2;
加排他锁就查不到,因为排他锁与共享锁不能存在同一数据上:
select * from innodb_lock where id = 2 for update; -- 阻塞,当然阻塞也可以使用UNLOCK TABLES来解决
行锁分析:
执行下面的命令,可以获取行锁锁信息:
SHOW STATUS LIKE 'innodb_row_lock%';
参数说明:
当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待
然后根据分析结果着手制定优化策略
MySQL集群架构(后面的了解即可):
MySQL高可用设计:
高可用介绍:
什么是高可用性:
维基百科的解释是: 高可用性(英语:High availability,缩写为 HA),IT术语
指系统无中断地执行其功能的能力,代表系统的可用性程度。是进行系统设计时的准则之一
高可用性系统与构成该系统的各个组件相比可以更 长时间运行
计算公式:A表示可用性,MTBF表示平均故障间隔,MTTR表示平均恢复时间
高可用有一个标准,9越多代表越容错,可用性越高
假设系统一直能够提供服务,我们说系统的可用性是100%
如果系统每运行100个时间单位,会有1个时间单位无法提供服务,我们说系统的可用性是99%
很多公司的高可用目标是4个9,也就是99.99%
MySQL高可用介绍 :
我们在考虑MySQL数据库的高可用的架构时,主要要考虑如下几方面:
如果数据库发生了宕机或者意外中断等故障,能尽快恢复数据库的可用性
尽可能的减少停机时间,保证业务不会因为数据库的故障而中断
用作备份、只读副本等功能的非主节点的数据应该和主节点的数据实时或者最终保持一致
当业务 发生数据库切换时,切换前后的数据库内容应当一致,不会因为数据缺失或者数据不一致而影响业务
客户端通过Master对数据库进行写操作,slave端进行读操作,并可进行备份,Master出现问题后,可以手动将应用切换到slave端
MySQL高可用集群方案 :
主从复制+读写分离 :
此种架构,一般初创企业比较常用,也便于后面步步的扩展,客户端通过Master对数据库进行写操作,slave端进行读操作
并可进行备份,Master出现问题后,可以手动将应用切换到slave端
主从复制的优点:
1:实时灾备,用于故障切换(高可用)
2:读写分离,提供查询服务(读扩展)
3:数据备份,避免影响业务(高可用)
读写分离的优点:
1:主从只负责各自的写和读,极大程度的缓解锁争用
2:从库可配置myisam引擎,提升查询性能以及节约系统开销
3:从库同步主库,通过主库发送来的binlog恢复数据
4:读写分离适用与读远大于写的场景(读的服务器多),但如果读服务器只有一台或者很少,或者就只有一台服务器
当select很多时,update和delete会被 这些select访问中的数据堵塞
因为访问太多了,一般很难同步或者阻塞,阻塞对只有一台来说,同步是对少的读服务器来说
那么一般需要等待select结束,即并发性能不高
即对于写和读比例相近的应用,应该部署双主相互复制,看如下的解释
双主从复制 :
很多企业刚开始都是使用MySQL主从模式,一主多从、读写分离等
但是单主如果发生单点故障,从库 切换成主库还需要作改动
因此,如果是双主或者多主,就会增加MySQL入口,提升了主库的可用性
双主模式是指两台服务器互为主从,任何一台服务器数据变更,都会通过复制应用到另外一方的数据库中
问题:使用双主双写还是双主单写?
建议大家使用双主单写,因为双主双写存在以下缺点:
1:ID冲突
在A主库写入,当A数据未同步到B主库时,对B主库写入,如果采用自动递增容易发生ID主键的冲 突
可以采用MySQL自身的自动增长步长来解决,例如A的主键为1,3,5,7…等等,以此类推
B的主键为2,4,6,8…等等,但是对数据库运维、扩展都不友好
2:更新丢失
同一条记录在两个主库中进行更新,会发生前面覆盖后面的更新丢失
即后面的更新慢,或者没有提交等等,即对应的写操作先后顺序不确定
很明显上面的双主,其中一个主可用看成备用,虽然也用来操作读,但是却可用变成主,其他的从一般不能,所以说他是主的备用
MMM架构:
MMM(Master-Master Replication Manager for MySQL)是一套用来管理和监控双主复制,支持双 主故障切换 的第三方软件
MMM 使用Perl语言开发,虽然是双主架构,但是业务上同一时间只允许一 个节点进行写入操作
下图是基于MMM实现的双主高可用架构
MMM故障处理机制:
MMM 包含writer和reader两类角色,分别对应写节点和读节点
1:当 writer节点出现故障,程序会自动移除该节点上的VIP
2:写操作切换到 Master2,并将Master2设置为writer
3:将所有Slave节点会指向Master2
除了管理双主节点,MMM 也会管理 Slave 节点,在出现宕机、复制延迟或复制错误
MMM 会移 除该节点的 VIP,直到节点恢复正常
MHA架构:
MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案
它由小日子(日本人)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件
在MySQL故障切换过程中,MHA能做到在0 ~ 30秒之内自动完成数据库的故障切换操作
并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用
目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器(一般不包括他本身)
一主二从,即一台充当master,一台充当备用master,另外一台充当从库
MHA Manager也可以管理多组主从复制:
该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)
MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上
MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时
它可以自动将最新数据的slave(通常是主的备用)提升为新的master,然后将所有其他的slave,重新指向新的master
整个故障转移过程对应用程序完全透明
MHA故障处理机制:
把宕机master的binlog保存下来
根据binlog位置点找到最新的slave
用最新slave的relay log修复其它slave,从而实现恢复宕机后的最新的数据
将保存下来的binlog在最新的slave上恢复
将最新的slave提升为master
将其它slave重新指向新提升的master,并开启主从复制
MHA优点:
自动故障转移快
主库崩溃不存在数据一致性问题
性能优秀,支持半同步复制和异步复制
一个Manager监控节点可以监控多个集群