注:毕笔记来自 黑马程序员 MySQL精通数据库入门。
文章目录
- 第1章 MySQL的架构介绍
-
- 1.1 MySQL简介
- 1.2 在Linux上安装MySQL
- 1.3 MySQL配置文件
- 1.3 MySQL介绍逻辑架构
- 1.4 MySQL存储引擎
-
- 1.4.0 存储引擎概述
- 1.4.1 查看命令
- 1.4.2 存储引擎特性
-
- 1.4.2.1 InnoDB
- 1.4.2.2 MyISAM
- 1.4.2.3 Memory
- 1.4.2 MyISAM和InnoDB
- 1.4.3 选择存储引擎
- 第2章 索引优化分析
-
- 2.1 概述
-
- 2.1.1 性能下降的原因
- 2.1.2 常用的join查询
-
- 2.1.2.1 SQL执行顺序
- 2.1.2.2 join图
- 2.2 索引简介
-
- 2.2.1 索引是什么
- 2.2.2 索引的优缺点
- 2.2.3 索引结构
-
- 2.2.3.1 概述
- 2.2.3.2 二叉树
- 2.2.3.3 B-Tree
- 2.2.3.4 B Tree
- 2.2.3.5 Hash
- 2.2.4 索引的分类
-
- 2.2.4.0 收集索引和二次索引
- 2.2.4.1 单值索引
- 2.2.4.2 唯一索引
- 2.2.4.3 主键索引
- 2.2.4.4 复合索引
- 2.2.5 基本语法
- 2.2.6 创建索引的机会
-
- 2.2.6.1 适合创建索引的情况
- 2.2.6.2 不适合创建索引
- 2.3 性能分析
-
- 2.3.0 SQL 执行频率
- 2.3.1 MySQL Query Optimizer
- 2.3.2 MySQL常见瓶颈
- 2.3.3 Explain
-
- 2.3.3.1 是什么
- 2.3.3.2 能干啥
- 2.3.3.3 怎么玩
- 2.3.3.4 字段解释
-
- 2.3.3.4.1 id
- 2.3.3.4.2 select_type
- 2.3.3.4.3 table
- 2.3.3.4.4 type
- 2.3.3.4.5 possible_keys
- 2.3.3.4.6 key
- 2.3.3.4.7 key_len
- 2.3.3.4.8 ref
- 2.3.3.4.9 rows
- 2.3.3.4.10 extra
- 2.4 索引优化
-
- 2.4.1 索引分析
-
- 2.4.1.1 单表
- 2.4.1.2 双表
- 2.4.1.3 三表
- 2.4.1.4 总结
- 2.4.2 索引失效
-
- 2.4.2.1 创建测试表
- 2.4.2.2 各种索引的各种情况
- 2.4.3 小总结
- 2.4.4 优化口诀
- 2.4.5 面试题讲解
- 2.4.6 一般性建议
- 2.5 其它索引
-
- 2.5.1 SQL 提示
- 2.5.2 覆盖索引
- 2.5.3 前缀索引
-
- 2.5.3.1 概述
- 2.5.3.2 语法
- 2.5.3.3 前缀索引长度
- 2.5.3.4 查询前缀索引的过程
- 2.6 索引设计原则
- 第3章 查询截取分析
-
- 3.0 插入优化
-
- 3.0.1 insert
-
- 3.0.1.1 优化方案一
- 3.0.1.2 优化方案二
- 3.0.1.3 优化方案三
- 3.0.2 大量插入数据
- 3.1 查询优化
-
- 3.1.1 永远小表驱动大表
- 3.1.2 order by关键字优化
-
- 3.1.2.1 order by优化
-
- 3.1.2.2.1 双路排序
- 3.1.2.2.2 单路排序
- 3.1.2.2.3 存在的问题
- 3.1.2.3 优化策略
- 3.1.2.4 总结
- 3.1.3 group by关键字优化
- 3.1.4 主键优化
-
- 3.1.4.1 数据组织
- 3.1.4.2 页分裂
- 3.1.4.3 页合并
- 3.1.4.4 索引设计原则
- 3.1.5 limit 优化
-
- 3.1.5.1 问题引入
- 3.1.5.2 优化思路
- 3.1.6 count 优化
-
- 3.1.6.1 问题引入
- 3.1.6.2 优化思路
- 3.1.6.3 `count` 用法
- 3.1.7 update 优化
- 3.2 慢查询日志
-
- 3.2.1 是什么
- 3.2.2 使用
- 3.2.3 日志分析工具mysqldumpslow
- 3.3 批处理数据脚本
- 3.4 Show Profile
-
- 3.4.1 是什么
- 3.4.2 使用
- 3.5 全局查询日志
-
- 3.5.1 配置文件来启用
- 3.5.2 编码来启用
- 3.6 错误日志
- 3.7 二进制日志
-
- 3.7.1 概述
- 3.7.2 格式
- 3.7.3 查看
- 3.7.4 删除
- 第4章 MySQL的锁机制
-
- 4.1 概述
- 4.2 三锁
-
- 4.2.0 全局锁
-
- 4.2.0.1 介绍
- 4.2.0.2 语法
- 4.2.0.3 特点
- 4.2.1 表级锁
-
- 4.2.1.1 介绍
- 4.2.1.2 表锁(偏读)
-
- 4.2.1.2.1 特点
- 4.2.1.2.2 案例分析
- 4.2.1.2.3 案例结论
- 4.2.1.2.4 表锁分析
- 4.2.1.3 元数据锁
- 4.2.1.4 意向锁
- 4.2.2 行锁(偏写)
-
- 4.2.2.1 特点
- 4.2.2.2 行锁支持事务
- 4.2.2.3 案例分析
- 4.2.2.4 案例结论
- 4.2.2.5 行锁分析
- 4.2.2.6 优化建议
- 4.2.3 页锁
- 第5章 主从复制
-
- 5.1 复制的基本原理
- 5.2 复制的基本原则
- 5.3 复制的最大问题
- 5.4 一主一从配置
- 5.5 主从复制的优势
- 第6章 MySQL管理
-
- 6.1 系统数据库
- 6.2 常用工具
-
- 6.2.1 mysql
- 6.2.2 mysqladmin
- 6.2.3 mysqlbinlog
- 6.2.4 mysqlshow
- 6.2.5 mysqldump
- 6.2.6 mysqlimport
- 6.2.7 source
- 第7章 InnoDB引擎
-
- 7.1 逻辑存储结构
-
- 7.1.1 表空间
- 7.1.2 段
- 7.1.3 区
- 7.1.4 页
- 7.1.5 行
- 7.2 架构
-
- 7.2.1 概述
- 7.2.2 内存结构
- 7.2.3 磁盘结构
- 6.2.4 后台线程
- 7.3 事务原理
-
- 7.3.1 事务基础
- 7.3.2 redo log
- 7.3.3 undo log
- 7.4 MVCC
-
- 7.4.1 基本概念
- 7.4.2 隐藏字段
-
- 7.4.2.1 介绍
- 7.4.3 undolog
-
- 7.4.3.1 介绍
- 7.4.3.2 版本链
- 7.4.4 readview
- 7.4.5 原理分析
-
- 7.4.5.1 RC隔离级别
- 7.4.5.3 RR 隔离级别
第1章 MySQL的架构介绍
1.1 MySQL简介
官网:https://www.mysql.com/
MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司
MySQL 是一种关联数据库管理系统(RDBMS), 将数据保存在不同的表中,而不是将所有数据放在一个大的仓库内,这样就增加了速度并提高了灵活性。
MySQL 特征
- MySQL 是开源的,所以你不需要支付额外的费用。
- MySQL 支持大型的数据库。可以处理拥有处理上千万条记录的大型数据库。
- MySQL 可以允许于多个系统上,并且支持多种语言,这些编程语言包含 C、C++、Java、Perl、PHP 、 Eiffel 、 Ruby 和 Tcl 等。
- MySQL对 PHP 有很好的支持, PHP 是暮年最流行的 WEB 开发语言之一。
- MySQL 支持大型数据库, 支持 5000W 条数据记录的数据仓库, 32 为操作系统最大可支持 4GB , 64位操作系统最大的表文件为 8TB。
- MySQL 是可以支持定制的,采用了 GPL协议, 你可以修改源码来开发自己的MySQL 系统。
1.2 在Linux上安装MySQL
MySQL高级的应用都是在Linux系统上进行,是对数据库的优化。
请参考博客:在Linux中安装MySQL
1.3 MySQL配置文件
Windows系统下mysql的配置文件为my.ini文件,Linux系统下mysql的配置文件为/etc/my.cnf文件。
MySQL主要配置文件如下:
- 二进制日志log-bin:用于主从复制
- 错误日志log-error:默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等
- 查询日志log:默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
- 数据文件
- 两系统
- mysql安装目录下的data目录下可以挑选很多库
- 默认路径:/var/lib/mysql
- frm文件:存放表结构
- myd文件:存放表数据
- myi文件:存放表索引
- 两系统
1.3 MySQL逻辑架构介绍
总体架构:
总体架构说明:
1、:提供客户端和连接服务,包含本地Sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信,主要完成一些类似于连接处理、授权认证、及相关的安全方案,在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程,同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。注:分配数据库连接线程池,控制数据库的连接和关闭等资源。
2、:主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的操作。所有跨操作引擎的功能也在这一层实现,如过程,函数等。在该层服务器会解析查询并创建相应的内部解析树,并对其完成相应的优A化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是SELECT语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。注:提供SQL操作的接口,对SQL脚本按一定规则进行解析,并通过SQL优化器优化执行顺序,对于查询的语句还会进入缓存区,提升系统的性能。
3、:存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引[擎进行通信,不同的存储引|擎具有的功能不同,这样我们可以根据自己的实际需要进行选选取。注:存储引擎都是可插拔的,每个存储引l擎所提供的服务都有所差异,所以我们需要根据具体的业务需要,选择合适的存储引[擎,常用的只有两种MyISAM和lnnoDB。数据库中的索引是在存储引擎层实现的。
4、:主要是将数据存储在运行于裸设备的文件系统上,并完成与存储引擎的交互。注:将数据存储到磁盘上,并协同存储引擎对数据进行读写操作。是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询 日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
数据库逻辑结构共分为四层,分别是连接层(线程连接池)、业务逻辑处理层(SQL解析读取)、数据存储引擎层(存储擎)、数据存储层(数据存储)和其它的数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用,主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离,这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1.4 MySQL存储引擎
1.4.0 存储引擎概述
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎。
1.4.1 查看命令
语法如下:
CREATE TABLE 表名(
字段1 字段1数据类型 [ COMMENT 字段1注释 ],
...
字段n 字段n数据类型 [ COMMENT 字段n注释 ]
) ENGINE = 引擎名 [ COMMENT 表注释 ];
引擎名可以是 InnoDB、MyISAM、Memory。例如:
create table my_myisam(
id int,
name varchar(10)
) engine = MyISAM;
可以通过如下命令查看支持的存储引擎:
show engines; #查看支持的存储引擎
通过如下命令查看当前默认的存储引擎:
show variables like '%storage_engines%'; #查看当前默认的存储引擎
语法:
# 语法
show create table 表名;
# 示例,查看 account 表的建表语句
show create table account;
1.4.2 存储引擎特点
1.4.2.1 InnoDB
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB是默认的 MySQL 存储引擎。
- DML 操作遵循 ACID 模型,支持事务。
- 行级锁,提高并发访问性能。
- 支持外键 FOREIGN KEY 约束,保证数据的完整性和正确性。
xxx.idb:其中 xxx 表示的是表名,如果使用了 InnoDB 存储引擎,那么数据库中的每张表都会对应这样一个表空间文件,存储该表的表结构、数据和索引。在早期版本中是以 .frm 后缀结尾的。
这些文件是二进制文件,是不能直接使用记事本程序打开的,但可以使用 MySQL 提供的一个命令 ibd2sdi,通过该命令就可以从 .ibd 文件中提取 sdi 信息,而 sdi 数据字典就包含该表的表结构。命令格式如下:
# 语法
ibd2sdi 表名.ibd
# 示例,查看 user.ibd 表空间文件的数据字典
ibd2sdi user.ibd
注:在 MySQL 中,变量 innodb_file_per_table 表示是否开启对于使用了 InnoDB 存储引擎的表,每一张表都对应一个 .ibd 文件。其中 ON 表示默认开启。
注:InnoDB 存储引擎的逻辑存储结构如下图(图来源于黑马视频):
- 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以包含多个 Segment 段。
- 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
- 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为 16K, 即一个区中一共有 64 个连续的页。
- 页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
- 行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段
1.4.2.2 MyISAM
MyISAM 是 MySQL 早起版本的默认存储引擎。
- 不支持事务。
- 不支持外键。
- 支持表锁,不支持行锁。
- 访问速度快。
MySQL 中使用了 MyISAM 存储引擎的表,会生成如下几个文件:
xxx.sdi:存储了表的结构信息。xxx.MYD:存储了表数据信息。xxx.MYI:存储了表索引信息。
1.4.2.3 Memory
Memory 引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用。
- 存储在内存中。
- 默认使用 Hash 索引。
使用了这种存储引擎的表会生成如下文件:
xxx.sdi:该文件存储了表结构信息。
1.4.2 MyISAM和InnoDB
1.4.3 存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
- InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。
- MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
- MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
第2章 索引优化分析
2.1 概述
2.1.1 性能下降原因
性能下降SQL慢,执行时间长,等待时间长的原因:
- 查询语句写的烂
- 索引失效:单值和复合
- 关联查询太多join(设计缺陷或不得已的需求)
- 服务器调优及各个参数设置不合理(缓冲、线程数等)
2.1.2 常用的join查询
2.1.2.1 SQL执行顺序
通常我们按照需求写的SQL查询语句是这样的:
SELECT DISTINCT查询列表
FROM 左表 INNER|LEFT|RIGHT
JOIN 右表 ON 连接条件
WHERE 分组前的筛选条件
GROUP BY 分组列表
HAVING 分组后的筛选条件
ORDER BY 排序列表
LIMIT 分页参数
我们手写的SQL中SELECT在最前面的位置。
随着 Mysql 版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。下面是经常出现的查询顺序:
FROM 左表 INNER|LEFT|RIGHT
JOIN 右表 ON 连接条件
WHERE 分组前的筛选条件
GROUP BY 分组列表
HAVING 分组后的筛选条件
SELECT DISTINCT查询列表
ORDER BY 排序列表
LIMIT 分页参数
而在机读中FROM是最先执行的。
2.1.2.2 join图
A表独有部分+AB两表的公有部分。
-- SQL语句如下:
SELECT 查询列表 FROM A LEFT JOIN B ON A.key=B.key# 其中A.key=B.key指的是连接条件
-- 注:
-- 左外连接中左边的是主表,右边的是从表
-- SQL语句如下:
SELECT 查询列表 FROM A RIGHT JOIN B ON A.key=B.key# 其中A.key=B.key指的是连接条件
-- 注:
-- 右外连接中右边的是主表,左边的是从表
获取的是两张表的公有部分。
-- SQL语句如下:
SELECT 查询列表 FROM A INNER JOIN B ON A.key=B.key# 其中A.key=B.key指的是连接条件
-- 注:
-- 内连接求的是多张表的交集部分
- 左外连接去除交集
SELECT 查询列表 FROM A LEFT JOIN B ON A.key=B.key WHERE B.key IS NULL;# 将从表B的连接条件作为NULL值判断
- 右外连接去除交集
SELECT 查询列表 FROM A RIGHT JOIN B ON A.key=B.key WHERE A.key IS NULL;# 将从表A的连接条件作为NULL值判断
- 全外连接
注意:MySQL不支持full join。
SELECT 查询列表 FROM A FULL JOIN B ON A.key=B.key;# 全外连接就是求并集
# 可以间接完成效果
SELECT 查询列表 FROM A LEFT JOIN B ON A.key=B.key
UNION
SELECT 查询列表 FROM A RIGHT JOIN B ON A.key=B.key
- 全外连接去除交集
SELECT 查询列表 FROM A FULL JOIN B ON A.key=B.key WHERE A.key IS NULL OR B.key IS NULL;
实例如下:
-- 左外连接
select * from beauty left join boys on beauty.boyfriend_id=boys.id;
-- 右外连接
select * from beauty right join boys on beauty.boyfriend_id=boys.id;
-- 内连接
select * from beauty inner join boys on beauty.boyfriend_id=boys.id;
-- 左外连接去除交集
select * from beauty left join boys on beauty.boyfriend_id=boys.id where boys.id IS NULL;
-- 右外连接去除交集
select * from beauty right join boys on beauty.boyfriend_id=boys.id where beauty.boyfriend_id IS NULL;
-- 全外连接(MySQL不能运行)
select * from beauty full join boys on beauty.boyfriend_id=boys.id;
-- 全外连接去除交集(MySQL不能运行)
select * from beauty full join boys on beauty.boyfriend_id=boys.id where boys.id IS NULL OR beauty.boyfriend_id IS NULL;
2.2 索引简介
2.2.1 索引是什么
MySQL 官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构(有序)。可以得到索引的本质:索引是数据结构。可以简单理解为。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址 。 为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指 针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hashindex)等。
一般来说索引本身也很大,不可能全部存储在内存中,因此。
如下演示使用索引与不使用索引的区别(假如表结构和数据如下):
假如我们要执行的 SQL 语句是:select * from user where age = 45;。
- 如果是无索引的情况,则会从第一行一直扫描到最后一行,即全表扫描,性能很低。
- 如果是有索引的情况,假设索引结构是二叉树(并不是真实的索引结构),对该表建立索引,即对
age字段建立一个二叉树的索引结构。在进行查询时,只需要扫描三次就可以查到,极大的提高了查询效率。
2.2.2 索引的优缺点
- 类似大学图书馆建书目索引,,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以。
- 虽然索引大大提高了查询速度,同时却,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
- 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就,或优化查询语句。
2.2.3 索引结构
2.2.3.1 概述
MySQL 的索引在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
索引结构
描述
B+Tree索引
最常见的索引类型,大部分引擎都支持 B+ 树索引
Hash索引
底层数据结构是用哈希表实现的, 只有精确匹配索引列的查询才有效, 不支持范围查询
R-tree(空间索引)
空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-text(全文索引)
是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES
上述是MySQL中所支持的所有的索引结构,接下来,我们再来看看不同的存储引擎对于索引结构的支持情况:
索引
InnoDB
MyISAM
Memory
B+tree索引
支持
支持
支持
Hash 索引
不支持
不支持
支持
R-tree 索引
不支持
支持
不支持
Full-text
5.6版本之后支持
支持
不支持
注:平常所说的索引,如果没有特别指明,都是指 B+ 树结构组织的索引。
2.2.3.2 二叉树
注:通过下面这几节可以了解为什么索引结构采用 B+ 树的数据结构。
假如 MySQL 的索引结构采用二叉树,比较理想的情况如图:
如果是按主键顺序插入,在比较极端的情况下,则会变成一个单链表,那么查找又会变成全表扫描。结构如下:
因此使用二叉树作为索引结构,缺点如下:
- 顺序插入时,会退化成一个链表,查询性能大大降低。
- 大数据量情况下,即使是比较理想的二叉树结构,也会有很多层,造成层次比较深,那么检索速度会变慢。
为了解决顺序插入而退化成链表的问题,可以考虑使用红黑树,红黑树是一颗自平衡二叉树,即使顺序插入数据,最终也会形成一颗平衡的二叉树,而不会退化成链表。
但即使如下,由于红黑树本身也是二叉树,所以大数据量层次比较深的问题仍然存在。使用红黑树的缺点如下:
- 大数据量情况下,即使是理想的红黑树,也会有很多层,造成层次比较深,那么检索速度会变慢。
所以,在 MySQL 的索引结构中,没有选择二叉树或红黑树,而是选择 B+ 树来作为数据结构。
2.2.3.3 B-Tree
B-Tree,B 树是一种多叉路衡查找树,相对于二叉树,B 树每个节点可以有多个分支,即多叉。
而多叉就能解决深层次的问题,将原来的高度问题变成了宽度问题(高瘦——>矮胖)。
以一颗最大度数(max-degree,度数即指一个节点的子节点个数)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:
注:可通过 B-Trees 网站来动态演示查看 B-Tree 的执行过程。
例如插入一组数据:[100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250]。然后观察一些数据插入过程中,节点的变化情况。如图所示:
B-Tree的特点:
- 5阶的B树,每一个节点最多存储4个key,对应5个指针。
- 一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。
- 在B树中,非叶子节点和叶子节点都会存放数据。
为什么不使用 B-Tree 作为实现索引的数据结构呢?因为对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。
2.2.3.4 B+Tree
B+Tree是B-Tree的变种,我们以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一下其结构示意图:
如图,可以看到 B+Tree 只在叶子结点存储数据:
- 绿色框框起来的部分,是索引部分,仅仅起到索引数据的作用,不存储数据。
- 红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据。
例如插入一组数据:[100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250]。然后观察一些数据插入过程中,节点的变化情况。如图所示:
B+Tree 与 B-Tree 相比,主要有以下三点区别:
- 所有的数据都会出现在叶子节点。
- 叶子节点形成一个单向链表。
- 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
上述我们所看到的结构是标准的B+Tree的数据结构,而 MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree(实际上是循环双链表),提高区间访问的性能,利于排序。
2.2.3.5 Hash
MySQL 中除了支持 B+Tree 索引,还支持一种索引类型——Hash索引。
注:如果想要了解 Hash 索引更多的信息,建议先学习了解 Hash 数据结构。
哈希索引就是采用一定的 hash 算法,将键值换算成新的 hash 值(通常是一个字符串转换成一个数字,再将数字映射到对应表中位置),映射到对应的槽位上,然后存储在hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了 hash 冲突(也称为 hash 碰撞),可以通过链表(但如果链表中冲突的结点多了话也会变成线性遍历查找了)来解决,如在 Java 中的 HashMap 中后期版本中采用了红黑树来解决冲突。
Hash 索引的特点如下:
- Hash索引只能用于对等比较(
=,in),不支持范围查询(between,>,<,…)。 - 无法利用索引完成排序操作,因为元素是乱序存放的。
- 查询效率高,通常(不存在 hash 冲突的情况)只需要一次检索就可以了,效率通常要高于 B+Tree 索引。
在 MySQL 中,支持 hash 索引的是 Memory 存储引擎。 而 InnoDB 中具有自适应 hash 功能,hash 索引是 InnoDB 存储引擎根据 B+Tree 索引在指定条件下自动构建的。
为什么 InnoDB 存储引擎选择使用 B+tree 索引结构?
- 相对于二叉树,层级更少,搜索效率高。
- 对于 B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。
- 相对 Hash 索引,B+Tree支持范围匹配及排序操作。
2.2.4 索引的分类
索引可以分为单值索引、唯一索引、主键索引、复合索引和全文索引。
分类
含义
特点
关键字
主键索引
针对表中的主键创建的索引
默认自动创建,只能有一个
PRIMARY
唯一索引
避免同一个表中某数据列的值重复
可以有多个
UNIQUE
单值索引
只对某列建立索引
可以有多个
复合索引
对多个列一起建立索引
可以有多个
全文索引
全文索引查找的是文本中的关键词,而不是比较索引中的值
可以有多个
FULLTEXT
2.2.4.0 聚集索引与二级索引
而在在 InnoDB 存储引擎中,根据索引的存储形式(物理存储),又可以分为以下两种:
分类
含义
特点
聚集索引(Clustered Index)
将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据
必须有,而且只有一个
二级索引(Secondary Index)
将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键
可以存在多个
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引。
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
- 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
聚集索引(如以主键 id 建立聚集索引)和二级索引(如以 name 字段建立二级索引)的具体结构如下:
- 聚集索引的叶子节点下挂的是这一行的数据。
- 二级索引的叶子节点下挂的是该字段值对应的主键值。
当我们执行如下的SQL语句(select * from user where name='Arm';)时,具体的查找过程如下:
- 1、由于是根据
name字段进行查询,所以先根据name='Arm'到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到Arm对应的主键值10。 - 2、由于查询返回的数据是
*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。 - 3、最终拿到这一行的数据,直接返回即可。
思考题:以下两条SQL语句,那个执行效率高 为什么
- A.
select * from user where id = 10;- B.
select * from user where name = 'Arm';备注: id 为主键,name 字段创建的有索引; 解答:A 语句的执行性能要高于B 语句。因为A语句直接走聚集索引,直接返回数据。而B语句需要先查询name字段的二级索引,然后再查询聚集索引,也就是需要进行回表查询。
2.2.4.1 单值索引
即一个索引只包含单个列,一个表可以有多个单列索引。
创建单值索引的语法如下:
-- 创建情况一:在创建表时创建
CREATE TABLE 表名(
字段名 字段类型 [约束],
字段名 字段类型 [约束],
...
字段名 字段类型 [约束],
KEY(字段名)# 这一句就是创建单列索引的语句,直接在KEY()中写字段名即可
);
-- 创建情况二:单独创建单值索引
CREATE INDEX 索引名 ON 表名(字段名);
# 注释:
# 1.索引名通常是idx_表名_字段名这样的格式,比如idx_user_name
# 2.单独创建索引的示例:CREATE INDEX idx_user_name user(nmae);
2.2.4.2 唯一索引
即索引列的值必须唯一,但允许有空值。
创建唯一索引的语法如下:
-- 创建情况一:在创建表时创建
CREATE TABLE 表名(
字段名 字段类型 [约束],
字段名 字段类型 [约束],
...
字段名 字段类型 [约束],
UNIQUE(字段名)# 这一句就是创建唯一索引的语句,直接在UNIQUE()中写字段名即可
);
-- 创建情况二:单独创建唯一索引
CREATE UNIQUE INDEX 索引名 ON 表名(字段名);
# 注释:
# 1.索引名通常是idx_表名_字段名这样的格式,比如idx_user_name
# 2.单独创建唯一索引的示例:CREATE UNIQUE INDEX idx_user_id user(id);
2.2.4.3 主键索引
设定某字段为主键后,数据库会自动建立索引,innodb存储引擎的主键为聚簇索引。
创建索引的基本语法如下:
-- 创建情况一:在创建表时创建
CREATE TABLE 表名(
字段名 字段类型 [约束],
字段名 字段类型 [约束],
...
字段名 字段类型 [约束],
PRIMARY KEY(字段名)# 这一句就是创建主键索引的语句,直接在PRIMARY KEY()中写字段名即可
);
-- 创建情况二:单独创建主键索引
ALTER TABLE 表名 ADD PRIMARY KEY 表名(字段名);
# 注释:
# 1.创建示例:ALTER TABLE user ADD PRIMARY KEY user(id);
-- 删除主键索引
ALTER TABLE 表名 DROP PRIMARY KEY;
# 注释:
# 1.删除示例:ALTER TABLE user DROP PRIMARY KEY;
# 2.如果要修改主键索引,那么必须先删除掉原索引,再新建索引
2.2.4.4 复合索引
即一个索引包含多个列。
创建的基本语法如下:
-- 创建情况一:在创建表时创建
CREATE TABLE 表名(
字段名 字段类型 [约束],
字段名 字段类型 [约束],
...
字段名 字段类型 [约束],
KEY(字段名,字段名,..)# 这一句就是创建复合索引的语句,直接在KEY()中写多个字段名即可
);
-- 创建情况二:单独创建复合索引
CREATE INDEX 索引名 ON 表名(字段名,字段名,...);
注:在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。因为单列索引很可能由于需要查询多个字段触发回表查询,而使用联合索引可以触发覆盖索引的情况避免回表查询,效率更高点。
2.2.5 基本语法
-- 语法
CREATE [UNIQUE] INDEX 索引名 ON 表名(字段名[,字段名,字段名,..]);
-- 语法
DROP INDEX 索引名 ON 表名;
-- 语法
SHOW INDEX FROM 表名;
-- 语法
-- 通过修改语句添加主键索引
ALTER TABLE 表名 ADD PRIMARY KEY(字段名);# 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为 NULL。
-- 通过修改语句添加普通索引
ALTER TABLE 表名 ADD INDEX 索引名(字段名,[字段名,..]);# 添加普通索引
-- 通过修改语句添加全文索引
ALTER TABLE 表名 ADD FULLTEXT 索引名(字段名,[字段名,..]);# 该语句指定了索引为FULLTEXT,用于全文索引
2.2.6 索引的创建时机
2.2.6.1 适合创建索引的情况
- 主键自动建立唯一索引;
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 单键/组合索引的选择问题, 组合索引性价比更高
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录,还会更新索引,加重了IO负担。
- where条件里用不到的字段不创建索引。
2.2.6.2 不适合创建索引的情况
- 表记录太少
- 经常增删改的表或者字段
- Where 条件里用不到的字段不创建索引(注:虽然提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件)
- 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。(注:比如国籍,全是中国人,那么没必要创建索引;又或者性别,不是’男’就是’女’也没必要创建索引)
2.3 性能分析
2.3.0 SQL 执行频率
我们可以获取数据库中 INSERT、UPDATE、DELETE、SELECT 这几类命令的执行频率,查看哪一类的命令执行频率比较高,如果是 SELECT 查询命令执行频次比较高,则需要针对查询进行优化。
MySQL 客户端连接成功后,通过 show session status; 或 show global status; 语句可以提供服务器状态信息。
通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT 的访问频次:
SHOW GLOBAL STATUS LIKE 'Com_______';
主要字段说明如下:
Com_delete:表示删除命令的执行频次。Com_insert:表示插入命令的执行频次。Com_select:表示查询命令的执行频次。Com_update:表示更新命令的执行频次。
注:通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据。如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。
通过查询SQL的执行频次,我们就能够知道当前数据库到底是增删改为主,还是查询为主。 那如果是以查询为主,该如何定位针对于那些查询语句进行优化呢? 我们可以借助于慢查询日志来获取哪些查询语句执行比较慢,然后针对这部分语句来进行优化。
2.3.1 MySQL Query Optimizer
1、Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
2、当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQLQuery Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。
2.3.2 MySQL常见瓶颈
- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈: top,free, iostat和vmstat来查看系统的性能状态
2.3.3 Explain
2.3.3.1 是什么
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
EXPLAIN或者DESC命令获取 MySQL 如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。
2.3.3.2 能干啥
- 表的读取顺序
- 数据读取操作的操作类型哪些索引可以使用
- 哪些索引被实际使用表之间的引用
- 每张表有多少行被优化器查询
2.3.3.3 怎么玩
-- 语法
EXPLAIN 查询语句;
-- 示例
EXPLAIN SELECT * FROM user;
查询出来所包含的信息如下:
示例:
2.3.3.4 字段解释
Explain 执行计划中各个字段的含义:
字段
含义
id
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。
select_type
表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
type
表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、 index、all 。
possible_key
显示可能应用在这张表上的索引,一个或多个。
key
实际使用的索引,如果为NULL,则没有使用索引。
key_len
表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。
filtered
表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好。
2.3.3.4.1 id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
- id相同,执行顺序由上至下
- id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
- id相同不同,同时存在
说了表的读取顺序:先加载括号内的子查询。
2.3.3.4.2 select_type
select_type有如下类型值:
- :简单的select查询,查询中不包含子查询或者UNION。
- :查询中若包含任何复杂的子查询,则最外层的查询被标记为PRIMARY。
- :在SELECT或WHERE列表中包含的子查询。
- :在FROM列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表中。
- :若第二个SELECT出现在UNION之后,则被标记为UNION。若UNION包含在FROM子句的子查询中,则外层SELECT将被标记为DERIVED。
- :从UNION表获取结果的SELECT。
查询的类型select_type主要用于区别普通查询、联合查询、子查询等复杂查询。
2.3.3.4.3 table
显示这一行的数据是关于哪张表的。
2.3.3.4.4 type
一般上百万条数据才进行优化。
type是访问类型排列,显示查询使用了何种类型,跟索引优化有很大的关系,需掌握。type的值有如下几种:
- :表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。
- :表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
- :唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
- :非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
- :只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
- :全索引扫描,index与ALL的区别为index只遍历索引树而非全表。通常比ALL快,因为索引文件通常比数据文件小。也就是all和index都是读全表,但index从索引中读取,而all从硬盘中读取。
- :全表扫描,将遍历全表找到匹配的行。
。
注:一般来说,得保证查询至少达到range级别,最好能达到ref。
如果百万级别或千万级别的记录查询出现的type是all,那么就需要考虑优化了。
2.3.3.4.5 possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出, 但不一定被查询实际使用。是。
2.3.3.4.6 key
。如果为NULL,则没有使用索引。
通常同possible_keys来说,possible_keys是理论上可能会被用到的索引,而key是实际上用到的索引。例如请客估计应该来10人,这是possible_keys,而当天实际上来了6人,这是key。
若查询中使用了覆盖索引,则该索引和查询的select字段重叠。所谓的覆盖索引就是查询的字段正好是复合索引中的字段列表,那么就直接在索引中查找,而不是从全表中查找,如下图:
2.3.3.4.7 key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的利用上了索引。ken_len 越长,说明索引使用的越充分。但使用的长度越小越好。
如何计算:
- ①先看索引上字段的类型+长度比如 int=4 ; varchar(20) =20 ; char(20) =20
- ②如果是 varchar 或者 char 这种字符串字段,视字符集要乘不同的值,比如 utf-8 要乘 3,GBK 要乘 2,
- ③varchar 这种动态字符串要加 2 个字节
- ④允许为空的字段要加 1 个字节
第一组:key_len=age 的字节长度+name 的字节长度=4+1 + (20*3+2)=5+62=67
第二组:key_len=age 的字节长度=4+1=5
2.3.3.4.8 ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
如"const"表示一个常量,"mytest.emp.deptno"表示"mytest"数据库的"emp"表的"deptno"列的索引被使用了。
2.3.3.4.9 rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
2.3.3.4.10 extra
extra中包含不适合在其他列中显示,但又十分重要的额外信息。
它可能的值有如下几种情况(重点关于①、②、③的情况):
①
说明mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为“文件排序”。
注:出现了这个值就应该对查询语句进行优化了。
出现 filesort 的情况:
优化后,不再出现 filesort 的情况: