文章目录
MySQL 逻辑架构简介
MySQL逻辑架构图
从上图可知,MySQL的逻辑架构主要分为四层
从上到下依次是连接层,服务层,引擎层,存储层
假设现在有一个select 查询请求
第一步就是和数据库链接(连接层),
链接完毕将查询请求给服务层,服务层对查询进行优化(服务层)
优化的结果给引擎层,选择合适的引擎(引擎层)
选完引擎,将数据交给存储层 (存储层)
四层逻辑架构功能介绍
-
连接层
最上层是一些客户端和连接服务,包含本地socket 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP 的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限
-
服务层(主要进行 sql 语句相关的工作)
第二层架构主要完成大多数的核心服务功能,如 sql 接口,并完成缓存的查询, sql 的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化,如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select 语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能
-
引擎层(可拔插的,就像汽车引擎可随便换)
存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需求进行选取。后面介绍 MyISAM 和 InnoDB
-
存储层
数据存储层,主要用于将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互
逻辑架构图各模块介绍
(对应上面那张图)
-
Connectors
指的是不同语言中与 sql 的交互
-
Management Serveices & Utilities
系统管理和控制工具
-
Connection Pool(连接池)
管理缓冲用户连接,线程处理等需要缓存的需求。
负责监听对MySQL server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。每一个连接上 MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。而连接线程的主要工作就是负责 MySQL Server 与客户端的通信。
接收客户端的命令请求,传递 Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的 cache 等
-
SQL Interface(SQL接口)
接收用户的 sql 命令,并且返回用户需要查询的结果。比如 select from 就是调用 SQL Interface
-
Parser(解析器)
SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的,是一个很长的脚本。在 MySQL中我们习惯将所有 Client 端发送给 Server 端的命令都称为 query ,在 MySQL Server 里面,连接线程接收到客户端的一个 Query 后,会直接将该 query 传递给专门负责将各种 Query 进行分类然后转发给各个对应的处理模块。
主要功能:
a 、 将SQL语句进行语义和语法的分析,分解成数据结构,然后按照不同的操作类型进行分类,然后做出针对性的转发到后续步骤,以后SQL语句的传递和处理就是基于这个结构的;
b、 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的。 -
Optimizer(查询优化器)
查询优化器:SQL语句在查询之前会使用查询优化器对查询进行优化。就是优化客户端请求query,根据客户端请求的 query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个 query 语句的结果。
使用的是“选取-投影-联接”策略进行查询:
用一个例子就可以理解: select uid,name from user where gender = 1;
这个select 查询先根据where 语句进行选取,而不是先将表全部查询出来以后再进行gender过滤;然后根据uid和name进行属性投影,而不是将属性全部取出以后再进行过滤。最后将这两个查询条件联接起来生成最终查询结果。 -
Cache和Buffer(查询缓存)
查询缓存:主要功能是将客户端提交 给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值 做一个对应。该 query 所取数据的基表发生任何数据的变化之后, MySQL 会自动使该 query 的Cache 失效。在读写比例非常高的应用系统中, Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。
如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等。 -
存储引擎接口
MySQL区别于其他数据库的最重要的特点就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关,可能是每个数据库系统本身都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每个存储引擎开发者都可以按照自己的意愿来进行开发。
MySQL的配置文件
二进制文件 log-bin :主从复制
错误日志 log-error:默认关闭,记录警告和错误、启动和关闭详情
查询日志 log :默认关闭,记录查询的sql ,开启会降低整体性能
数据文件:
- frm文件: 存放表结构
- myd文件:存放表数据
- myi文件:存放表索引
存储引擎简介
- 存储引擎是基于表的,而不是数据库
MyISAM 和 InnoDB 的区别
如何用命令查看你的 mysql 现在已提供什么存储引擎
show engines;
查询结果:
查看你的 mysql 当前默认的存储引擎
show variables like '%storage_engine%';
查询结果:
索引优化
SQL 性能下降的原因
- 查询语句写的不好
- 索引失效(建了没用上)
- 关联查询太多 join(设计缺陷或不得已的需求)
- 服务器调优以及各个参数设置不合理(缓冲、线程数等)
SQL执行的加载顺序
我们自己写的sql的顺序
机器读的顺序(前面 逻辑架构 parser干的事情)
总结:sql 的执行顺序可以通过下图了解,注意 sql 的执行是从 from 开始的
七种 JOIN 理论
1. INNER JOIN
A 表和 B表共有,也就是 A 和 B 表的交集
sql 语句如下:
select <select_list>
from A
(inner) join B
on A.key=B.key
2. LEFT JOIN
left jion:A独有+AB共有(交集)
sql 语句如下:
select <select_list>
from A
left join B
on A.Key=B.key
3. RIGHT JOIN
right join:B 独有+ AB 共有
sql 语句如下 :
select <select_list>
from A
right join B
on A.key=B.key
4. A 独有
和left join差不多,只是把A和B的交集去掉了,所以是在left join 上改的代码,如下:
select <select_list>
from A
left join B
on A.Key=B.key
where
B.key is null
5. B独有
sql代码如下:
select <select_list>
from A
right join B
on A.Key=B.Key
where A.Key is null
6. AB 全有(并集)
sql 语句如下:
select <select_list> from A left join B on A.Key=B.key //左连接
union
select <select_list> from A right join B on A.key=B.key //右连接
7. AB独有
A 独有 并上 B独有
sql 语句如下:
select <select_list> from A left join B on A.Key=B.key where B.key is null // A 独有
union
select <select_list> from A right join B on A.Key=B.Key where A.Key is null // B 独有
索引简介
索引的本质
参考链接:https://blog.csdn.net/dataiyangu/article/details/79828275
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是一种数据结构。
数据库查询是数据库的主要功能之一,最基本的查询算法是顺序查找(linear search)时间复杂度为O(n),显然在数据量很大时效率很低。优化的查找算法如二分查找(binary search)、二叉树查找(binary tree search)等,虽然查找效率提高了。但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织)。所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构。这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。
看一个例子:
图中展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)的复杂度内获取到相应数据。
虽然这是一个货真价实的索引,但是实际的数据库系统几乎没有使用二叉查找树或其进化品种红黑树(red-black tree)实现的。
索引的优势
- 提高数据的检索效率,降低数据库的IO成本
- 通过索引对数据进行排序,降低数据排序成本,降低了 CPU 的消耗
索引的劣势
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL不仅要保存数据,还要保存索引文件每次更新添加了索引列的字段,都会调整因为更新带来的键值变化后的索引信息
- 索引只是提高效率的一个因素,如果你的MySQL 有大数据量的表,就需要 花时间研究建立最优秀的索引,或优化查询语句
索引的基本语法
创建索引
create [unique] index indexname on tablename(columnname(length));
alter table tablename add index indexname (columnname(length));
注意:如果是CHAR,VARCHAR类型,length可以小于字段实际长度;
如果是BLOB和TEXT类型,必须指定length。
删除索引
drop index indexname on tablename;
查看索引
SHOW INDEX FROM table_name\G
使用 alter 命令添加索引的四种方式
// 1. 主键索引,意味着索引的值必须是唯一的,且不能为null
ALTER TABLE `table_name` ADD PRIMARY KEY (`column_list`)
// 2.唯一索引,这条语句创建的索引的值必须是唯一的(除 null 以外,null 可能会出现多次)
ALTER TABLE `table_name` ADD UNIQUE (`column_list`)
//3.添加普通索引,索引值可能出现多次
ALTER TABLE `table_name` ADD INDEX index_name (`column_list` )
//4.添加全文索引,
ALTER TABLE `table_name` ADD FULLTEXT (`column_list`)
MySQL索引分类
- 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引
- 唯一索引:索引列的值必须唯一,但允许有空值
- 复合索引:即一个索引包含多个列
索引的结构:
①BTREE索引;②Hash索引;③Full-Text索引;④R-Tree索引。
注意:
一个表的索引最好不要超过5个
同一个时间只能使用一个索引
MySQL 索引结构
- BTree 索引
哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询的条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引,加重IO负担
- Where条件里用不到的字段不创建索引
- 单间/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
- 查询中统计或者分组字段
哪些情况不要创建索引
- 表记录太少
- 经常增删改的表
- 数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。
注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
性能分析
前提知识
MySQL Query Optimizer(前面MySQL逻辑架构的,优化器)
MySQL常见瓶颈
- CPU:CPU在饱和的时候一般发生在数据装入在内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量时
- 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
Explain
Explain
Explain 是什么
查看执行计划
使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是
如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈
Explain 能做什么
- 表的读取顺序(id)
- 数据读取操作的操作类型(select_type)
- 哪些索引可以使用(prossible_keys)
- 哪些索引被实际使用(key)
- 表之间的引用(ref)
- 每张表有多少行被优化器查询(rows)
Explain 语法
直接 Explain + sql 语句
当使用 Explain select * from user; 可以得到 sql 语句执行的相关信息
explain 查询结果的表头,各个字段如下
查询结果:
Explain 每个字段简介
Column | 含义 |
---|---|
id | 查询序号 |
select_type | 查询类型 |
table | 表名 |
partitions | 匹配的分区 |
type | join类型 |
prossible_keys | 可能会选择的索引 |
key | 实际选择的索引 |
key_len | 索引的长度 |
ref | 与索引作比较的列 |
rows | 要检索的行数(估算值) |
filtered | 查询条件过滤的行数的百分比 |
Extra | 额外信息 |
Explain每个字段详解
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
id 值有三种情况:
- id 相同,执行顺序从上到下
- id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
- id相同不同,同时存在
derived2 中的2指的是 id=2
select_type
有哪些类型
查询的类型,主要用于区别
普通查询、联合查询、子查询等的复杂查询
-
SIMPLE
简单的select查询,查询中不包含子查询或者UNION操作
无论查询语句多么复杂,执行计划中 select_type 为 simple 的单位查询一定只有一个。最外层的 select 查询的 select_type 为 simpleexplain select * from user
查询结果:
-
PRIMARY
查询中若包含任何复杂的子部分,最外层查询则被标记为 PRIMARY
一个需要 Union 操作或含子查询的select 查询执行计划中,位于最外层的 select_type 即为 primary
与 simple 一样, select_type为primary的单位select查询也只存在1个,位于查询最外侧的select单位查询的select_type为primaryexplain select username, (select role_name from role where id=user_role.rid ) as username_role from user,user_role;
查询结果:
-
SUBQUERY
在SELECT或者WHERE列表中包含了子查询explain select username from `user` where id=( select uid from account where money=2000 )
查询结果:
-
DERIVED
在FROM列表中包含的子查询被标记为DERIVED(衍生)
MySQL会递归执行这些子查询,把结果放在临时表里。explain select t.username from (select username from user) t
查询结果:
-
UNION
若第二个SELECT出现在UNION之后,则被标记为UNION;
若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED由union操作联合而成的单位select查询中,除第一个外,第二个以后的所有单位select查询的select_type都为union。union的第一个单位select的select_type不是union,而是DERIVED。它是一个临时表,用于存储联合(Union)后的查询结果。
explain select * from( (select username from user u1) union (select username from user u2) union (select username from user u3) ) p
查询结果:
-
UNION RESULT
从UNION表获取结果的SELECT,如上图,后面跟的数字都是指的id号
table
显示这一行的数据是关于哪个表的
type
原文链接:https://blog.csdn.net/dennis211/article/details/78170079
type 的结果和 sql 是否优化过,是否是最佳状态息息相关
type意味着类型,这里的type官方全称是“join type”,意思是“连接类型”,这样很容易给人一种错觉觉得必须需要俩个表以上才有连接类型。事实上这里的连接类型并非字面那样的狭隘,它更确切的说是一种数据库引擎查找表的一种方式,在《高性能mysql》一书中作者更是觉得称呼它为访问类型更贴切一些。
mysql5.7中type的类型达到了14种之多,这里只记录和理解最重要且经常遇见的六种类型,它们分别是all,index,range,ref,eq_ref,const。从左到右,它们的效率依次是增强的。撇开sql的具体应用环境以及其他因素,你应当尽量优化你的sql语句,使它的type尽量靠右,但实际运用中还是要综合考虑各个方面的。
type一共有以下几种值
从最好到最差依次是:system > const > eq_ref > ref > range > index > ALL
一般来说,得保证查询至少达到 range 级别,最好能达到 ref
system
表只有一行记录,这是const 类型的特例,平时不会出现,这个也可以忽略不计
const
通常情况下,如果将一个主键放置到where后面作为条件查询,mysql优化器就能把这次查询优化转化为一个常量。至于如何转化以及何时转化,这个取决于优化器。
eq_ref
ref_eq 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个?什么情况下结果集只有一个呢!那便是使用了主键或者唯一性索引进行查找的情况,比如根据学号查找某一学校的一名同学,在没有查找前我们就知道结果一定只有一个,所以当我们首次查找到这个学号,便立即停止了查询。这种连接类型每次都进行着精确查询,无需过多的扫描,因此查找效率更高,当然列的唯一性是需要根据实际情况决定的。
ref
出现该连接类型的条件是: 查找条件列使用了索引而且不为主键和unique。其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。下面为了演示这种情形,给employee表中的name列添加一个普通的key(值允许重复)
alter table employee add key I_EMPLOYEE_NAME(`name`);
接下来,在employee表中根据name查找数据的时候,mysql优化器便选择了ref的连接类型。
mysql> explain select * from employee where `name` = '张三';
+----+-------------+----------+------+----------------+----------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+----------------+----------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | employee | ref | I_EMPLOYEE_NAM | I_EMPLOYEE_NAM | 62 | const | 1 | Using index condition |
+----+-------------+----------+------+----------------+----------------+---------+-------+------+-----------------------+
range
range指的是有范围的索引扫描,相对于index的全索引扫描,它有范围限制,因此要优于index。关于range比较容易理解,需要记住的是出现了range,则一定是基于索引的。同时除了显而易见的between,and以及’>’,’<'外,in和or也是索引范围扫描。
index
这种连接类型只是另外一种形式的全表扫描,只不过它的扫描顺序是按照索引的顺序。这种扫描根据索引然后回表取数据,和all相比,他们都是取得了全表的数据,而且index要先读索引而且要回表随机取数据,因此index不可能会比all快(取同一个表数据),但为什么官方的手册将它的效率说的比all好,唯一可能的原因在于,按照索引扫描全表的数据是有序的。这样一来,结果不同,也就没法比效率的问题了。
如果一定要比效率,只需要获取这个表的数据并且排序便可以看出来谁比谁效率高了:
mysql> explain select * from employee order by `no` ;
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | employee | ALL | NULL | NULL | NULL | NULL | 5 | Using filesort |
+----+-------------+----------+------+---------------+------+---------+------+------+----------------+
mysql> explain select * from employee order by rec_id ;
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
| 1 | SIMPLE | employee | index | NULL | PRIMARY | 4 | NULL | 5 | NULL |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------+
上面可以看出,根据no列排序的连接类型是all型的,但是注意extra列是用到了排序(Using filesort),而根据rec_id列排序的连接类型是index,而且得到的结果自然是有序的,不许额外的排序。可能正是因为这个缘故,index的效率比all高,但注意这需要相同的条件才成立(既需要排序)。
如果连接类型为type,而且extra列中的值为‘Using index’,那么称这种情况为 索引覆盖
;
索引覆盖意味着什么呢?想象这样一种场景,如果说一本新华字典是一张表,当然前面的索引部分(假设按照部首的索引)是这张表的索引,那么索引覆盖就相当于根据部首索引获取第一个字到最后一个字(新华字典的所有字)。我们获得了字典中所有的字,然而我们并没有查一次表,因为我们想要的都在索引中,即索引覆盖。
- 索引覆盖可以解决模糊查询问题
mysql> explain select rec_id from employee ;
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | employee | index | NULL | PRIMARY | 4 | NULL | 5 | Using index |
+----+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
上例获取的rec_id刚好为索引列,因此无需回表取数据。
ALL:表示全表扫描
这便是所谓的“全表扫描”,如果是展示一个数据表中的全部数据项,倒是觉得也没什么,如果是在一个查找数据项的sql中出现了all类型,那通常意味着你的sql语句处于一种最原生的状态,有很大的优化空间。
为什么这么说呢?因为all是一种非常暴力和原始的查找方法,非常的耗时而且低效。用all去查找数据就好比这样的一个情形:S学校有俩万人,我告诉你你给我找到小明,然后你怎么做呢!你当然是把全校俩万人挨个找一遍,即使你很幸运第一个人便找到了小明,但是你仍然不能停下,因为你无法确认是否有另外一个小明存在,直到你把俩万人找完为止。所以,基本所有情况,我们都要避免这样类型的查找,除非你不得不这样做。
以employee表为例,下面一种情形便是all类型的查找:
mysql> explain select * from employee where `no` = '20150001';
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | employee | ALL | NULL | NULL | NULL | NULL | 5 | Using where |
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
这是因为no列既不是主键也不是索引,因此只能采用全表扫描来查找目标no。
Tip:const 和 eq_ref 区别:
简单地说是const是直接按主键或唯一键读取,eq_ref用于联表查询的情况,按联表的主键或唯一键联合查询。
possible_keys
显示可能应用在这张表中的索引,一个或多个。
查询涉及的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key
- 实际使用的索引。如果为null则没有使用索引
- 查询中若使用了
覆盖索引
,则索引和查询的select字段重叠
作用:
用来判断是否使用到了索引,即索引是否失效
在多个索引竞争的情况下,MySQL究竟用到了哪个索引
覆盖索引:
简单来说就是我们select 后面的字段刚好和我们建的复合索引完全一致(个数和顺序)
比如 :
select col1,col2 from table
我们的复合索引刚好就是(col1,col2),即我们想要查找的数据都在索引中,就叫索引覆盖
补充:
key_len
- 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好
- key_len显示的值为索引最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
ref
如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func
rows
rows 也是一个重要的字段。根据表统计信息及索引选用情况,大致估算出找到记录锁需要读取的行数
这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好.
Extra
-
Using filesort
当 Extra 中有 Using filesort 时, 表示 MySQL 需额外的排序操作, 不能通过索引顺序达到排序效果. 一般有 Using filesort, 都建议优化去掉, 因为这样的查询 CPU 资源消耗大. -
Using temporary
查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高, 建议优化.
使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和 分组查询 group by -
Using index
表示相应的select操作中使用了覆盖索引(Coveing Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找;
如果没有同时出现using where,表面索引用来读取数据而非执行查找动作。 -
Using where
表面使用了where过滤
-
Using join buffer
使用了连接缓存
-
impossible where
where子句的值总是false,不能用来获取任何元组
例如 select * from user where id=1 and and!=1 -
select tables optimized away
在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者
对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,
查询执行计划生成的阶段即完成优化。 -
distinct
优化distinct,在找到第一匹配的元组后即停止找同样值的工作
explain 案例
索引优化
- 全值匹配
- 最左前缀法则
- 不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描
- 存储引擎不能使用索引中范围条件右边的列
- 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select*
- mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
- is null,is not null 也无法使用索引
- like以通配符开头(’$abc…’)mysql索引失效会变成全表扫描操作
- 字符串不加单引号索引失效
- 少用or,用它连接时会索引失效
MySQL 调优的步骤
1.观察,至少跑一天,看看生产的慢SQL的情况
2.开启慢查询日志,设置阈值,比如超过5s的就是慢SQL,并将它抓取出来
3.explain + 慢SQL分析
4.show profile
5.运维经理 or DBA ,进行SQL数据库服务器的参数调优
总结:
- 慢查询的开启并捕获
- explain + 慢SQL分析
- show profile 查询SQL 在 MySQL 服务器里面的执行细节和生命周期情况
- SQL 数据库服务器的参数调优
查询优化
1. 小表驱动大表
为什么要小表驱动大表
- 大表具有索引,查询大表的时间是 O(logn)
- 大表全表扫描,磁盘块查询速度快
我的理解(可能不对):类似嵌套循环
for (int i=0;i<5;i++)
for(int j=0;j<1000;j++)
表其实是一个文件,读一个表进行一次IO,一次IO可以把整张表读进来。如果小的循环(小表)在外层,那每次都可以将大表一次性读入内存。但是如果大表在外层的话,每读一行就要开关IO一次,IO操作是很耗费时间的,应该减少IO操作。所以小表在外面好一点,还有就是小表在外面的时候,大表有索引,检索快。
例如:表A(小表),表B(大表)
select * from A where cc in(select cc from B) -->效率低,用到了A表上cc列的索引;
select * from A where exists(select cc from B where cc=A.cc) -->效率高,用到了B表上cc列的索引。
相反的:
select * from B where cc in(select cc from A) -->效率高,用到了B表上cc列的索引
select * from B where exists(select cc from A where cc=B.cc) -->效率低,用到了A表上cc列的索引
小表驱动大表的案例 in & exists
in 的原理:(当B表的数据集必须小于A表时,用in 优于 exists)
select * from A where id in (select id from B)
等价于
for select id from B
for select * from A where A.id=B.id
in 语法理解:以上查询使用了in语句,in()只执行一次,它查出B表中的所有id字段并缓存起来。之后,检查A表的id是否与B表中的id是否相等,如果相等则将A表的记录加入结果集中,直到遍历完A表的所有记录
exists 的原理:(当A表的数据集必须小于B表时,用exists 优于 in)
select * from A where exists (select 1 from B where B.id=A.id)
等价于
for select * from A
for select * from B.id=A.id
也就是说外层循环是小表
exists 语法可以理解为: 将主查询的数据,放到子查询中做条件验证,根据验证结果(true 或 false)来决定主查询的数据结果是否得以保留
Tip:
- exists 只返回 true 或 false ,因此子查询中的 select * 也可以是 select 1 或select ‘x’ ,官方的说法是实际执行时会忽略select 清单,因此没有区别
- exists 子查询的实际过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题
- exists 子查询往往也可以用条件表达式,或其他子查询或者 join 来代替,何种最优需要具体问题具体分析
2. order by 关键字优化
-
order by 子句尽量使用 index 方式排序,避免使用 Filesort 排序
MySQL支持两种方式的排序,FileSort和Index,Index效率高。它指MySQL扫描索引本身完成排序。FileSort方式效率较低。
order by 满足两情况,会使用 index 排序(就是能用到索引):
- ORDER BY语句使用索引最左前列
- 使用where子句与OrderBy子句条件列组合满足索引最左前列
- 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
-
如果不在索引列上,filesort有两种算法:双路排序和单路排序
什么是单路排序,双路排序?
双路排序: 按照sql 指定的条件将符合的数据从磁盘中读取出来,把每一条数据参与排序的列以及这一行数据在磁盘中的位置(row point 又叫指针信息) 丢给缓冲区,就是 sort buffer,在缓冲区完成排序,之后将排序结果丢给随机缓冲区,随机缓冲区根据指针信息再回到磁盘中读取该行数据的其他信息,之后再将合并结果返回给客户端。很明显这种排序方法产生了两次IO操作
单路排序:按照sql 指定的条件将符合的数据从磁盘中全部读取出来,之后丢给缓冲区 sort buffer ,缓冲区根据用户指定的排序条件完成排序,将结果返回给客户端。这种排序方式是在mysql 4.1之后出现的,选择单路排序需要一定条件。
相比之下,单路排序比双路排序确实减少了IO次数,降低了磁盘开销,但是这里必须提出,减少IO操作的代价是增加了内存消耗。这是典型的用空间换时间的
单路排序存在的问题
有可能单路排序算法一次拿不出数据,那么就还比双路排序更消耗IO,效率更慢在sort_ buffer中, 单路排序比双路排序要多占用很多空间,因为单路排序是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_ buffer的容量, 导致每次只能取sort buffer容 量大小的数据,进行排序(创建tmp文件,多路合并),排完再去取
sort_ buffer容量 大小,再排… 从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。
如何解决(优化策略):
- 增大sort buffer size参数的设置
- 增大 max_length_for_sort_data 参数的设置
小总结
3. group by 关键字优化
和 order by 的优化没什么不同,就是多了一条
groupby实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
where高于having,能写在where限定的条件就不要去having限定了。
慢查询日志
概述
-
MySQL的慢查询日志是MySQL提供的一 种日志记录,它用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_ query_ time值的SQL,则会被记录到慢查询日志中。
-
具体指运行时间超过long_ query_ time值的SQL,则会被记录到慢查询日志中。long_ query_ time的默认值为10s,意思是运行10秒以上的语句。
-
由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sq|执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
用法
注意:
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件
开启慢查询日志:
set global slow_query_log=1; //=1 开启, =0 关闭慢查询日志
使用 set global slow_query_log=1 开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效
如果要永久生效,就必须修改配置文件 my.ini(windows下的话,Linux 是 my.cnf) ,其他系统变量也是如此
查看是否开启慢查询日志:
show VARIABLES like '%slow_query_log%'
结果:
查看慢查询的阈值时间
show VARIABLES like '%long_query_time%'
结果:
自己设置慢查询时间的阈值
set global long_query_time=3;
注意:运行时间大于 long_query_time 才会被记录下来,正好等于的不会被记录下来
一个休眠4s的慢查询:
select sleep(4)
结果:
查看自己的慢查询日志,我的路径是C:\ProgramData\MySQL\MySQL Server 5.6\data:
C:\Program Files\MySQL\MySQL Server 5.6\bin\mysqld.exe, Version: 5.6.31-log (MySQL Community Server (GPL)). started with:
TCP Port: 3306, Named Pipe: (null)
Time Id Command Argument
# Time: 200508 23:29:08
# User@Host: root[root] @ localhost [127.0.0.1] Id: 82
# Query_time: 4.000049(查询时间) Lock_time: 0.000000(锁) Rows_sent: 1 Rows_examined: 0
use eesy_mybatis; //(使用的哪个数据库)
SET timestamp=1588951748; //时间戳
select sleep(4); // 导致问题的sql
查询当前系统又多少条慢 sql 记录:
show global status like '%slow_queries%'
结果:
如果再增加一条select sleep(4)
结果:
配置版:
日志分析工具 mysqldumpslow
因为直接分析日志文件是个体力活,因此mysql为我们提供了相关工具mysqldumpslow来对慢查询日志文件进行分析。
具体使用方式可用mysqldumpslow --help命令查看具体参数,常见参数如下
案例
批量插入数据脚本
函数 & 存储过程
函数有返回值,过程没有
函数
delimiter:
其实就是告诉mysql解释器,该段命令是否已经结束了,mysql是否可以执行了。默认情况下,delimiter是分号;。在命令行客户端中,如果有一行命令以分号结束,那么回车后,mysql将会执行该命令。
创建表
- 创建部门表(dept)
create table dept(
id int unsigned primary key auto_increment,
deptno mediumint unsigned not null default 0,
dname varchar(20) not null default '',
loc varchar(13) not null default ''
)engine=innodb default charset=utf8;
- 创建员工表(empt)
create table emp(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0,/*编号*/
empname varchar(20) not null default '',/*名字*/
job varchar(9) not null default '',/*工作*/
mgr mediumint unsigned not null default 0,/*上级编号*/
hiredate date not null,/*入职时间*/
sal decimal(7,2) not null,/*薪水*/
comm decimal(7,2) not null,/*红利*/
deptno mediumint unsigned not null default 0 /*部门编号*/
)engine=innodb default charset=utf8;
创建函数
- 创建函数随机产生字符串
delimiter $$ drop function if exists rand_string; create function rand_string(n int) returns varchar(255) begin declare chars_str varchar(52) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 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 if exists rand_num; create function rand_num() returns int(5) begin declare i int default 0; set i=floor(100+rand()*100); return i; end $$
注意:
由于在创建函数时,可能会报:This function has none of DETERMINISTIC…因此我们需开启函数创建的信任功能。
可通过set global log_bin_trust_function_creators=1的形式开启该功能,也可通过在my.cnf中永久配置的方式开启该功能,在[mysqld]下配置log_bin_trust_function_creators=1。
创建存储过程批量插入数据
- 往员工表 emp 表插入数据的存储过程
delimiter $$ drop procedure if exists insert_emp; create procedure insert_emp(in start int(10),in max_num int(10)) begin declare i int default 0; set autocommit=0; repeat set i=i+1; insert into emp(empno,empname,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6),'developer',0001,curdate(),2000,400,rand_num()); until i=max_num end repeat; commit; end $$
- 往部门表 dept 表插入数据的存储过程
delimiter $$ drop procedure if exists insert_dept; create procedure insert_dept(in start int(10),in max_num int(10)) begin declare i int default 0; set autocommit=0; repeat set i=i+1; insert into dept(deptno,dname,loc) values(rand_num(),rand_string(10),rand_string(8)); until i=max_num end repeat; commit; end $$
调用存储过程
call insert_dept(10,100);
call insert_emp(10,100);
查看结果:
dept表:
emp表:
删除函数和存储过程
// 删除函数
drop function rand_num;
drop function rand_string;
//删除存储过程
drop procedure insert_dept;
drop procedure insert_emp;
Show profiles
概述
是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优测量
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
分析步骤
1.是否支持,看看当前的SQL版本是否支持
show variables like 'profiling';
2.开启功能,默认是关闭,使用前需要开启
set profiling=1;
3.运行SQL
先运行了如下sql
select * from emp;
select * from dept;
select * from emp group by id%10 limit 10000;
select * from emp group by id%10 order by 5;
4.查看结果,show profiles
其中 Query_id 为每条执行SQL的编号,Duration 为执行时间,Query为SQL 语句
show profile for query Query_id 可以查询每个 Query_id 对应的SQL语句的资源消耗情况,如下图:
show profile for query 90;
查询结果:
其中 Status 表示 Query_id=90 的那条SQL 语句执行过程中经历的所有状态,Duration 表示每个状态持续的时间,执行该 SQL 的总时间为所有的 Duration加起来的总和
5.诊断SQL
查看 cpu 、 io 等的利用情况
show profile cpu,block io for query Query_id
查询结果:
以上的Status参数重要的主要是以下四个
- converting HEAP to MyISAM :查询结果太大,内存都不够用了往磁盘上搬了。
- Creating tmp table :创建临时表
- Copying to tmp table on disk :把内存中临时表复制到磁盘,危险!!!
- locked
出现了这几个参数,就该优化 sql
除了cpu 和 io 的其他参数
全局查询日志
全局查询日志用于保存所有的sql执行记录,该功能主要用于测试环境,在生产环境中永远不要开启该功能
配置启用
编码启用
// 开启全局日志
set global general_log=1;
// 输出格式
set global log_output='table';
//此后,你所编写的sql 语句将会被记录在mysql 库里的general_log表,可以用一下命令查看
select * from mysql.general_log;
MySQL 锁机制
概述
当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。
就像酒店的房间,如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他使用完毕才可以再次使用。
锁的分类
表锁
//手动增加表锁:
lock table 表名1 read(write), 表名2 read(write),其他
// 查看表上加过的锁
show open tables;
lock table user write 的查询结果,可以看到In_use=1
可以通过Table_locks_waited
和 Table_locks_immediate
状态变量来分析系统上的表锁定
show status like 'table%';
查询结果:
Table_locks_immediate:能够立即获得表级锁的锁请求次数
Table_locks_waited:不能立即获取表级锁而需要等待的锁请求次数
如果Table_locks_immediate / Table_locks_waited > 5000,最好采用InnoDB引擎,
因为InnoDB是行锁而MyISAM是表锁,对于高并发写入的应用InnoDB效果会好些。
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
结论:
读锁会阻塞写,但是不会阻塞读。写锁会阻塞读和写
行锁
InnoDB 引擎自带行锁
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
主从复制
原文链接:https://blog.csdn.net/weixin_43879074/article/details/88525006
一、什么是主从复制?
主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库,主数据库一般是准实时的业务数据库。您看,像在mysql数据库中,支持单项、异步赋值。在赋值过程中,一个服务器充当主服务器,而另外一台服务器充当从服务器。此时主服务器会将更新信息写入到一个特定的二进制文件中。并会维护文件的一个索引用来跟踪日志循环。这个日志可以记录并发送到从服务器的更新中去。当一台从服务器连接到主服务器时,从服务器会通知主服务器从服务器的日志文件中读取最后一次成功更新的位置。然后从服务器会接收从哪个时刻起发生的任何更新,然后锁住并等到主服务器通知新的更新
二、主从复制的作用(好处,或者说为什么要做主从)重点?
做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的评率,提高单个机器的I/O性能。
读写分离,使数据库能支持更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。
1–在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力;(主库写,从库读,降压)
2–在从主服务器进行备份,避免备份期间影响主服务器服务;(确保数据安全)
3–当主服务器出现问题时,可以切换到从服务器。(提升性能)
三、主从复制的原理?
1.数据库有个bin-log二进制文件,记录了所有sql语句。
2.我们的目标就是把主数据库的bin-log文件的sql语句复制过来。
3.让其在从数据的relay-log重做日志文件中再执行一次这些sql语句即可。
4.下面的主从配置就是围绕这个原理配置
5.具体需要三个线程来操作:
1.binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。在从库里,当复制开始的时候,从库就会创建两个线程进行处理:
2.从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。
3.从库的SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。
可以知道,对于每一个主从复制的连接,都有三个线程。拥有多个从库的主库为每一个连接到主库的从库创建一个binlog输出线程,每一个从库都有它自己的I/O线程和SQL线程。
主从复制如图 帮助理解:
四、主从复制的好处?
做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的评率,提高单个机器的I/O性能。
读写分离,使数据库能支持更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。
好处一:实现服务器负载均衡
通过服务器复制功能,可以在主服务器和从服务器之间实现负载均衡。即可以通过在主服务器和从服务器之间切分处理客户查询的负荷,从而得到更好地客户相应时间。通常情况下,数据库管理员会有两种思路。
一是在主服务器上只实现数据的更新操作。包括数据记录的更新、删除、新建等等作业。而不关心数据的查询作业。数据库管理员将数据的查询请求全部 转发到从服务器中。这在某些应用中会比较有用。如某些应用,像基金净值预测的网站。其数据的更新都是有管理员更新的,即更新的用户比较少。而查询的用户数 量会非常的多。此时就可以设置一台主服务器,专门用来数据的更新。同时设置多台从服务器,用来负责用户信息的查询。将数据更新与查询分别放在不同的服务器 上进行,即可以提高数据的安全性,同时也缩短应用程序的响应时间、提高系统的性能。
二是在主服务器上与从服务器切分查询的作业。在这种思路下,主服务器不单单要完成数据的更新、删除、插入等作业,同时也需要负担一部分查询作 业。而从服务器的话,只负责数据的查询。当主服务器比较忙时,部分查询请求会自动发送到从服务器重,以降低主服务器的工作负荷。当然,像修改数据、插入数 据、删除数据等语句仍然会发送到主服务器中,以便主服务器和从服务器数据的同步。
好处二:通过复制实现数据的异地备份
可以定期的将数据从主服务器上复制到从服务器上,这无疑是先了数据的异地备份。在传统的备份体制下,是将数据备份在本地。此时备份 作业与数据库服务器运行在同一台设备上,当备份作业运行时就会影响到服务器的正常运行。有时候会明显的降低服务器的性能。同时,将备份数据存放在本地,也 不是很安全。如硬盘因为电压等原因被损坏或者服务器被失窃,此时由于备份文件仍然存放在硬盘上,数据库管理员无法使用备份文件来恢复数据。这显然会给企业 带来比较大的损失。
而如果使用复制来实现对数据的备份,就可以在从服务器上对数据进行备份。此时不仅不会干扰主服务气的正常运行,而且在备份过程中主服务器可以继 续处理相关的更新作业。同时在数据复制的同时,也实现了对数据的异地备份。除非主服务器和从服务器的两块硬盘同时损坏了,否则的话数据库管理员就可以在最 短时间内恢复数据,减少企业的由此带来的损失。
好处三:提高数据库系统的可用性
数据库复制功能实现了主服务器与从服务器之间数据的同步,增加了数据库系统的可用性。当主服务器出现问题时,数据库管理员可以马上让从服务器作为主服务器,用来数据的更新与查询服务。然后回过头来再仔细的检查主服务器的问题。此时一般数据库管理员也会采用两种手段。
一是主服务器故障之后,虽然从服务器取代了主服务器的位置,但是对于主服务器可以采取的操作仍然做了一些限制。如仍然只能够进行数据的查询,而 不能够进行数据的更新、删除等操作。这主要是从数据的安全性考虑。如现在一些银行系统的升级,在升级的过程中,只能够查询余额而不能够取钱。这是同样的道理。
二是从服务器真正变成了主服务器。当从服务器切换为主服务器之后,其地位完全与原先的主服务器相同。此时可以实现对数据的查询、更新、删除等操 作。为此就需要做好数据的安全性工作。即数据的安全策略,要与原先的主服务器完全相同。否则的话,就可能会留下一定的安全隐患。
四 从数据库的读的延迟问题了解吗?如何解决?
主库宕机后,数据可能丢失
从库只有一个sql Thread,主库写压力大,复制很可能延时
解决方法:
半同步复制—解决数据丢失的问题
并行复制—-解决从库复制延迟的问题
https://blog.csdn.net/darkangel1228/article/details/80003967
主从复制和主主复制区别?
最大区别是 主从是对主操作数据,从会实时同步数据。反之对从操作,主不会同步数据,还有可能造成数据紊乱,导致主从失效。 主主则是无论对那一台操作,另一个都会同步数据。一般用作高容灾方案
参考链接:
id & exists :https://blog.csdn.net/qq_32800367/article/details/82467804