0. 目录
1)MySQL 总体架构介绍
2)MySQL 存储引擎调优
3)常用慢查询分析工具
4)如何定位不合理的 SQL
5)SQL 优化的一些建议
1 MySQL 总体架构介绍
1.1 MySQL 总体架构介绍
引言MySQL是一个关系型数据库应用十分广泛在学习任何一门知识之前对其架构有一个概括性的了解是非常重要的比如索引、sql是在哪个地方执行的流程是什么样的今天我们就先来学习一下MySQL的总体架构
总的来说:MySQL 架构是一个客户端 - 服务器系统。
MySQL 主要包括以下几部分:
Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
存储引擎:主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自己的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了
连接器:身份认证和权限相关 (登录 MySQL 的时候)。
查询缓存:执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)mysql 的 server 层增加一层缓存模块,类似一个内存的kv 层,k 是 sql,value 是结果
分析器:没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
优化器:按照 MySQL 认为最优的方案去执行。
执行器:执行语句,然后从存储引擎返回数据。
1.2 MySQL 存储引擎介绍
引言和大多数的数据库不同, MySQL中有一个存储引擎的概念针对不同的存储需求可以选择最优的存储引擎。存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式 。存储引擎是基于表的,而不是基于库的所以存储引擎也可被称为表类型。
MySQL 提供了插件式的存储引擎架构。所以 MySQL 存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎。
MySQL5.0支持的存储引擎包含 : InnoDB 、MyISAM 、BDB、MEMORY、MERGE、EXAMPLE、NDB Cluster、ARCHIVE、CSV、BLACKHOLE、FEDERATED等
可以通过指定 show engines , 来查询当前数据库支持的存储引擎 :
SHOW ENGINES;
表含义:- support : 指服务器是否支持该存储引擎- transactions : 指存储引擎是否支持事务- XA : 指存储引擎是否支持分布式事务处理- Savepoints : 指存储引擎是否支持保存点(实现回滚到指定保存点)
查看 MySQL 数据库存储引擎配置
SHOW VARIABLES LIKE '%storage_engine%';
1.2.1 如何更改数据库表引擎
建表语句后面加入引擎赋值即可,命令举例如下,CREATE TABLE t1(id INT ,name VARCHAR(20)) ENGINE = MyISAM;
修改已有的表引擎,命令举例如下,
ALTER TABLE t1 ENGINE = InnoDB;
1.2.2 常用引擎及其特性对比
常见的存储引擎 :
MyISAM 存储引擎 :访问快,不支持事务和外键。表结构保存在.frm 文件中,表数据保存在.MYD 文件中,索引保存在.MYI 文件中。
[root@linux-141 itcast]# ll-rw-r-----. 1 mysql mysql 8630 9月 10 16:01 t_account_myisam.frm-rw-r-----. 1 mysql mysql 52 9月 10 16:06 t_account_myisam.MYD-rw-r-----. 1 mysql mysql 2048 9月 10 17:56 t_account_myisam.MYI[root@linux-141 itcast]#
**innoDB 存储引擎 (**5.5 版本开始默认) : 支持事务,占用磁盘空间大,支持并发控制。表结构保存在.frm 文件中,如果是共享表空间,数据和索引保存在 innodb_data_home_dir 和 innodb_data_file_path 定义的表空间中,可以是多个文件。如果是多表空间存储,每个表的数据和索引单独保存在 .ibd 中。
[root@linux-141 itcast]# ll-rw-r-----. 1 mysql mysql 8630 9月 10 16:02 t_account_innodb.frm-rw-r-----. 1 mysql mysql98304 9月 14 15:50 t_account_innodb.ibd[root@linux-141 itcast]#
MEMORY 存储引擎 :内存存储,速度快,不安全,适合小量快速访问的数据。表结构保存在.frm 中。
特性对比 :
1.2.3 如何选择不同类型的引擎
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
以下是几种常用的存储引擎的使用环境。
InnoDB : 是 Mysql 的默认存储引擎,用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询以外,还包含更新、删除操作,那么 InnoDB 存储引擎是比较合适的选择。InnoDB 存储引擎除了有效的降低由于删除和更新导致的锁定, 还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB 是最合适的选择。MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。MEMORY:将所有数据保存在 RAM 中,在需要快速定位记录和其他类似数据环境下,可以提供极快的访问。MEMORY 的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY 表通常用于更新不太频繁的小表,用以快速得到访问结果。
1.3 SQL 的执行流程是什么样的
客户端发送一条查询给服务器。服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。服务器端进行 SQL 解析、预处理,再由优化器生成对应的执行计划。MySQL 根据优化器生成的执行计划,再调用存储引擎的 API 来执行查询。将结果返回给客户端。2 MySQL 存储引擎调优
2.1 MySQL服务器硬件优化
tips
硬件(cpu、内存等)相关
了解即可
关于提升硬件设备性能:
例如选择尽量高频率的 ** 内存(** 频率不能高于主板的支持)、提升网络带宽、使用 SSD 高速磁盘、提升 CPU 性能等。
CPU 的选择:
对于数据库并发比较高的场景,CPU 的数量比频率重要。对于 CPU 密集型场景和频繁执行复杂 SQL 的场景,CPU 的频率越高越好
磁盘的选择
影响数据库最大的性能问题就是磁盘 I/O 为提高数据库的 IOPS 性能,可使用 SSD 或 PCIE-SSD 高速磁盘设备
磁盘 IO 的优化
可以用 RAID 来进行优化
常用 RAID(磁盘阵列)级别:
RAID0:也称为条带,就是把多个磁盘链接成一个硬盘使用,这个级别 IO 最好 RAID1:也称为镜像,要求至少有两个磁盘,每组磁盘存储的数据相同 RAID5:也是把多个(最少 3 个)硬盘合并成一个逻辑盘使用,数据读写时会建立奇偶校验信息,并且奇偶校验信息和相对应的数据分别存储在不同的磁盘上。当 RAID5 的一个磁盘数据发生损坏后,利用剩下的数据和响应的奇偶校验信息去恢复被损坏的数据
RAID1+0(建议使用):就是 RAID0 和 RAID1 的组合。同时具备两个级别的优缺点,一般建议数据库使用这个级别。
2.2 MySQL 数据库配置优化
tips:
以下为生产环境中最常用的 DB 参数配置
表示缓冲池字节大小,大的缓冲池可以减少磁盘 IO 次数。 innodb_buffer_pool_size = 推荐值为物理内存的 50%~80%。
用来控制 redo log buffer 刷新到磁盘的策略。 innodb_flush_log_at_trx_commit=1
select @@innodb_flush_log_at_trx_commit;
0 : 提交事务的时候,不立即把 redo log buffer 里的数据刷入磁盘文件中,而是依靠 InnoDB 的主线程每秒执行一次刷新到磁盘。此时可能你提交事务了,结果 mysql 宕机了,然后此时内存里的数据全部丢失。1 : 提交事务的时候,立即把 redo log buffer 里的数据刷入磁盘文件中,只要事务提交成功,那么数据就必然在磁盘里了。2 : 提交事务的时候,把 redo log buffer日志写入磁盘文件对应的系统缓存,而不是直接进入磁盘文件,这时可能1秒后才会把系统缓存里的数据写入到磁盘文件。
每提交 1 次事务就同步写到磁盘中,可以设置为 1。 sync_binlog=1
0:默认值。事务提交后,将二进制日志从缓冲写入操作系统缓冲,但是不进行刷新操作(fsync()),此时只是写入了操作系统缓冲而没有刷新到磁盘,若操作系统宕机则会丢失部分二进制日志。1:事务提交后,将二进制文件写入磁盘并立即执行刷新操作,相当于是同步写入磁盘,不经过操作系统的缓存。N:每写N次操作系统缓冲就执行一次刷新操作。
脏页占 innodb_buffer_pool_size 的比例,触发刷脏页到磁盘。 推荐值为 25%~50%。 innodb_max_dirty_pages_pct=30
脏页:内存数据页和磁盘数据页上的内容不一致
后台进程最大 IO 性能指标。 默认 200,如果 SSD,调整为 5000~20000
PCIE-SSD 可调整为 5w 左右
默认:innodb_io_capacity=200
指定 innodb 共享表空间文件的大小。 innodb_data_file_path = ibdata:1G:autoextend:默认 10M,
一般设置为 1GB
慢查询日志的阈值设置,单位秒。 long_query_time=0.3
合理设置区间 0.1s~0.5s,
mysql 复制的形式,row 为 MySQL8.0 的默认形式。 binlog_format=row
建议 binlog 的记录格式为 row 模式
STATEMENT模式:每一条会修改数据的sql语句都会记录到binlog中。ROW模式:不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了,修改成什么样了。MIXED模式:以上两种模式的混合使用。
降低 interactive_timeout、wait_timeout 的值。
交互等待时间和非交互等待时间,值一致,建议 300~500s,默认 8 小时
在用mysql客户端对数据库进行操作时,打开终端窗口,如果一段时间(8小时)没有操作,再次操作时,会报错:当前的连接已经断开,需要重新建立连接
数据库最大连接数 max_connections=200
过大,实例恢复时间长;过小,造成日志切换频繁。 innodb_log_file_size = 默认
redo log 空间大小
全量日志建议关闭。 默认关闭 general_log=0
开启 general log 将所有到达MySQL Server的SQL语句记录下来,general_Log文件就会产生很大的文件,建议关闭
2.3 Mysql 中查询缓存优化
tips:
在 MySQL 8.0 之后废弃这个功能
原理:复杂、实用性不高
作为了解即可
1) 查询缓存概述
开启 Mysql 的查询缓存,当执行完全相同的 SQL 语句的时候,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存。
2) 操作流程
客户端发送一条查询给服务器;服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。否则进入下一阶段;服务器端进行 SQL 解析、预处理,再由优化器生成对应的执行计划;MySQL 根据优化器生成的执行计划,调用存储引擎的 API 来执行查询;将结果返回给客户端。回顾
3) 查询缓存配置
查看当前的 MySQL 数据库是否支持查询缓存:
SHOW VARIABLES LIKE 'have_query_cache';
2. 查看当前 MySQL 是否开启了查询缓存 :
SHOW VARIABLES LIKE 'query_cache_type';
3. 查看查询缓存的占用大小 :
SHOW VARIABLES LIKE 'query_cache_size';
4. 查看查询缓存的状态变量:
SHOW STATUS LIKE 'Qcache%';
各个变量的含义如下:
4) 开启查询缓存
MySQL 的查询缓存默认是关闭的,需要手动配置参数 query_cache_type , 来开启查询缓存。query_cache_type 该参数的可取值有三个 :
在 f 配置中,增加以下配置 :
#开启查询缓存query_cache_type=1
配置完毕之后,重启服务既可生效 ;
然后就可以在命令行执行 SQL 语句进行验证 ,执行一条比较耗时的 SQL 语句,然后再多执行几次,查看后面几次的执行时间;获取通过查看查询缓存的缓存命中数,来判定是否走查询缓存。
-- 执行SQL语句进行验证 查询缓存SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';-- 将SELECT修改为小写,发现缓存失效SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
5) 查询缓存 SELECT 选项
可以在 SELECT 语句中指定两个与查询缓存相关的选项 :
SQL_CACHE : 如果查询结果是可缓存的,并且 query_cache_type 系统变量的值为 ON 或 DEMAND ,则缓存查询结果 。
SQL_NO_CACHE : 服务器不使用查询缓存。它既不检查查询缓存,也不检查结果是否已缓存,也不缓存查询结果。
例子:
SELECT SQL_CACHE id, name FROM xxx;SELECT SQL_NO_CACHE id, name FROM xxx;
6) 查询缓存失效的情况
tips
需要注意的问题
1) SQL 语句不一致的情况, 要想命中查询缓存,查询的 SQL 语句必须一致。
SQL1 : select count(*) from xxx;SQL2 : Select count(*) from xxx;
2) 当查询语句中有一些不确定的值,则不会缓存。如 : now () , current_date () , curdate () , curtime () , rand () , uuid () , user () , database () 。
SQL1 : select * from xxx where updatetime < now() limit 1;SQL2 : select user();SQL3 : select database();
3) 不使用任何表查询语句。
select 'A';
4) 查询 mysql, information_schema 或 performance_schema 数据库中的表时,不会走查询缓存。
select * from information_schema.engines;
5) 在存储的函数,触发器或事件的主体内执行的查询。
6) 如果表更改,则使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除。这包括使用MERGE
映射到已更改表的表的查询。一个表可以被许多类型的语句,如被改变 INSERT, UPDATE, DELETE, TRUNCATE TABLE, ALTER TABLE, DROP TABLE,或 DROP DATABASE 。
将查询缓存关闭,因为后面还需要进行索引的验证,所以不希望走查询缓存
[root@linux-141 itcast]# vi /etc/f[root@linux-141 itcast]# service mysql restartShutting down MySQL.... SUCCESS!Starting MySQL. SUCCESS!
2.4. Mysql 内存管理及优化
1)内存优化原则
1) 将尽量多的内存分配给 MySQL 做缓存,但要给操作系统和其他程序预留足够内存。
2) MyISAM 存储引擎的数据文件读取依赖于操作系统自身的 IO 缓存,因此,如果有 MyISAM 表,就要预留更多的内存给操作系统做 IO 缓存。
3) 排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认值的设置要根据最大连接数合理分配,如果设置太大,不但浪费资源,而且在并发连接较高时会导致物理内存耗尽。
2) MyISAM 内存优化
MyISAM 存储引擎使用 key_buffer 缓存索引块,加速 myisam 索引的读写速度。对于 myisam 表的数据块,mysql 没有特别的缓存机制,完全依赖于操作系统的 IO 缓存。
key_buffer_size
key_buffer_size 决定 MyISAM 索引块缓存区的大小,直接影响到 MyISAM 表的存取效率。可以在 MySQL 参数文件中设置 key_buffer_size 的值,对于一般 MyISAM 数据库,建议至少将 1/4 可用内存分配给 key_buffer_size。
在 f 中做如下配置:
key_buffer_size=512M
read_buffer_size
如果需要经常顺序扫描 MyISAM 表,可以通过增大 read_buffer_size 的值来改善性能。但需要注意的是 read_buffer_size 是每个 session 独占的,如果默认值设置太大,就会造成内存浪费。
read_rnd_buffer_size
对于需要做排序的 MyISAM 表的查询,如带有 order by 子句的 sql,适当增加 read_rnd_buffer_size 的值,可以改善此类的 sql 性能。
但需要注意的是 read_rnd_buffer_size 是每个 session 独占的,如果默认值设置太大,就会造成内存浪费。
3) InnoDB 内存优化
innodb 用一块内存区做 IO 缓存池,该缓存池不仅用来缓存 innodb 的索引块,而且也用来缓存 innodb 的数据块。
innodb_buffer_pool_size
该变量决定了 innodb 存储引擎表数据和索引数据的最大缓存区大小。在保证操作系统及其他程序有足够内存可用的情况下,innodb_buffer_pool_size 的值越大,缓存命中率越高,访问 InnoDB 表需要的磁盘 I/O 就越少,性能也就越高。
innodb_buffer_pool_size=512M
innodb_log_buffer_size
决定了 innodb 重做日志缓存的大小,对于可能产生大量更新记录的大事务,增加 innodb_log_buffer_size 的大小,可以避免 innodb 在事务提交前就执行不必要的日志写入磁盘操作。
innodb_log_buffer_size=10M
2.5. Mysql 并发参数调整
从实现上来说,MySQL Server 是多线程结构,包括后台线程和客户服务线程。多线程可以有效利用服务器资源,提高数据库的并发性能。在 Mysql 中,控制并发连接和线程的主要参数包括 max_connections、back_log、thread_cache_size、table_open_cahce。
1) max_connections
最大可支持的连接数
采用 max_connections 控制允许连接到 MySQL 数据库的最大数量,默认值是 151。如果状态变量 connection_errors_max_connections 不为零,并且一直增长,则说明不断有连接请求因数据库连接数已达到允许最大值而失败,这时可以考虑增大 max_connections 的值。
Mysql 最大可支持的连接数,取决于很多因素,包括给定操作系统平台的线程库的质量、内存大小、每个连接的负荷、CPU 的处理速度,期望的响应时间等。在 Linux 平台下,性能好的服务器,支持 500-1000 个连接不是难事,需要根据服务器性能进行评估设定。
2) back_log
积压请求栈大小
back_log 参数控制 MySQL 监听 TCP 端口时设置的积压请求栈大小。如果 MySql 的连接数达到 max_connections 时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即 back_log,如果等待连接的数量超过 back_log,将不被授予连接资源,将会报错。5.6.6 版本之前默认值为 50 , 之后的版本默认为 50 + (max_connections / 5), 但最大不超过 900。
如果需要数据库在较短的时间内处理大量连接请求, 可以考虑适当增大 back_log 的值。
3) table_open_cache
执行线程可打开表缓存个数
该参数用来控制所有 SQL 语句执行线程可打开表缓存的数量, 而在执行 SQL 语句时,每一个 SQL 执行线程至少要打开 1 个表缓存。该参数的值应该根据设置的最大连接数 max_connections 以及每个连接执行关联查询中涉及的表的最大数量来设定 :
max_connections x N ;
4) thread_cache_size
缓存客户服务线程的数量
为了加快连接数据库的速度,MySQL 会缓存一定数量的客户服务线程以备重用,通过参数 thread_cache_size 可控制 MySQL 缓存客户服务线程的数量。
5)lock_wait_timeout
innodb_lock_wait_timeout
事务等待行锁的时间
该参数是用来设置 InnoDB 事务等待行锁的时间,默认值是 50ms , 可以根据需要进行动态设置。对于需要快速反馈的业务系统来说,可以将行锁的等待时间调小,以避免事务长时间挂起; 对于后台运行的批量处理程序来说, 可以将行锁的等待时间调大, 以避免发生大的回滚操作。
3 常用慢查询分析工具
引言在日常的业务开发中MySQL 出现慢查询是很常见的大部分情况下会分为两种情况1、业务增长太快2、要么就是SQL 写的太xx了所以对慢查询 SQL 进行分析和优化很重要其中 mysqldumpslow 是 MySQL 服务自带的一款很好的分析调优工具
3.1 调优工具 mysqldumpslow
3.1.1 调优工具常用设置
1、什么是 MySQL 慢查询日志
MySQL 提供的一种慢查询日志记录,用来记录在 MySQL 查询中响应时间超过阀值的记录 具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中
2、如何查看慢查询设置情况
慢查询的时间阈值设置
show variables like '%slow_query_log%';
解释
slow_query_log // 是否开启,默认关闭,建议调优时才开启slow_query_log_file // 慢查询日志存放路径
3、如何开启慢查询日志记录
1) 命令开启
set global slow_query_log =1; //只对当前会话生效,重启失效
执行成功
再次执行
show variables like '%slow_query_log%';
先关闭客户端连接,再进行重新连接,即可看到设置生效
发现开启了 mysqldumpslow 调优工具
mysql> show variables like '%slow_query_log%';+---------------------+-------------------------------------------+| Variable_name | Value |+---------------------+-------------------------------------------+| slow_query_log| ON|| slow_query_log_file | /opt/mysql-5.7.28/data/linux-141-slow.log |+---------------------+-------------------------------------------+2 rows in set (0.02 sec)mysql>
2)配置文件开启
vim f在[mysqld]下添加:slow_query_log = 1slow_query_log_file = /opt/mysql-5.7.28/data/linux-141-slow.log重启MySQL服务
修改并且重启后
发现开启了 mysqldumpslow 调优工具
mysql> show variables like '%slow_query_log%';+---------------------+-------------------------------------------+| Variable_name | Value |+---------------------+-------------------------------------------+| slow_query_log| ON|| slow_query_log_file | /opt/mysql-5.7.28/data/linux-141-slow.log |+---------------------+-------------------------------------------+2 rows in set (0.02 sec)mysql>
3)哪些 SQL 会记录到慢查询日志
-- 查看阀值(大于),默认10sshow variables like 'long_query_time%';
默认值是 10 秒
4)如何设置查询阀值
命令设置
-- 设置慢查询阀值set global long_query_time = 1;
备注:另外开一个 session 或重新连接 ,才会看到变化
执行成功发发现慢 sql 的时间变成了 1 秒
配置文件设置
vim f[mysqld]long_query_time = 1log_output = FILE重启MySQL服务
执行成功发发现慢 sql 的时间变成了 1 秒
5)如何把未使用索引的 SQL 记录写入慢查询日志
-- 查看设置,默认关闭show variables like 'log_queries_not_using_indexes';
我们发现,未使用索引的 sql 默认是不记录到慢查询日志的
开启配置
set global log_queries_not_using_indexes = on;
执行如下
6)模拟数据
-- 睡眠2s再执行select sleep(2);-- 查看慢查询条数show global status like '%Slow_queries%';
我们发现,每执行一次 select sleep (2),之后,再通过 show global status ... 命令,他的值就会 + 1
3.1.2 调优工具常用命令
语法格式
mysqldumpslow [ OPTS... ] [ LOGS... ] //命令行格式
常用到的格式组合
-s 表示按照何种方式排序c 访问次数l 锁定时间r 返回记录t 查询时间al 平均锁定时间ar 平均返回记录数at 平均查询时间-t 返回前面多少条数据-g 后边搭配一个正则匹配模式,大小写不敏感
1、拿到慢日志路径
show variables like '%slow_query_log%';
日志路径为:/opt/mysql-5.7.28/data/linux-141-slow.log
查看日志
[root@linux-141 mysql-5.7.28]# cat /opt/mysql-5.7.28/data/linux-141-slow.log/opt/mysql-5.7.28/bin/mysqld, Version: 5.7.28-log (MySQL Community Server (GPL)). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argument# Time: -09-15T01:40:31.342430Z# User@Host: root[root] @ [192.168.36.1] Id:2# Query_time: 2.000863 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0use itcast;SET timestamp=1631670031;-- 睡眠2s再执行select sleep(2);[root@linux-141 mysql-5.7.28]#
2、得到访问次数最多的 10 条 SQL
[root@linux-141 mysql-5.7.28]# ./bin/mysqldumpslow -s r -t 10 /opt/mysql-5.7.28/data/linux-141-slow.log-bash: ./bin/mysqldumpslow: /usr/bin/perl: 坏的解释器: 没有那个文件或目录[root@linux-141 mysql-5.7.28]# yum -y install perl perl-devel[root@linux-141 mysql-5.7.28]# ./bin/mysqldumpslow -s r -t 10 /opt/mysql-5.7.28/data/linux-141-slow.log
3、按照时间排序的前 10 条里面含有左连接的 SQL
[root@linux-141 mysql-5.7.28]# ./bin/mysqldumpslow -s t -t 10 -g "left join" /opt/mysql-5.7.28/data/linux-141-slow.logReading mysql slow query log from /opt/mysql-5.7.28/data/linux-141-slow.logDied at ./bin/mysqldumpslow line 167, <> chunk 28.[root@linux-141 mysql-5.7.28]#
3.1.3 慢日志文件分析
1、查看慢查询日志
[root@linux-141 mysql-5.7.28]# cat /opt/mysql-5.7.28/data/linux-141-slow.log/opt/mysql-5.7.28/bin/mysqld, Version: 5.7.28-log (MySQL Community Server (GPL)). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argument# Time: -09-15T01:40:31.342430Z# User@Host: root[root] @ [192.168.36.1] Id:2# Query_time: 2.000863 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0use itcast;SET timestamp=1631670031;-- 睡眠2s再执行select sleep(2);# Time: -09-15T01:50:32.130305Z# User@Host: root[root] @ [192.168.36.1] Id:2# Query_time: 3.001904 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0SET timestamp=1631670632;select sleep(3);# Time: -09-15T01:50:55.064372Z# User@Host: root[root] @ [192.168.36.1] Id:2# Query_time: 4.008082 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0SET timestamp=1631670655;select sleep(4);# Time: -09-15T01:51:01.343463Z# User@Host: root[root] @ [192.168.36.1] Id:2# Query_time: 5.007035 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0SET timestamp=1631670661;select sleep(5);# Time: -09-15T01:51:07.737834Z ###### 执行SQL时间# User@Host: root[root] @ [192.168.36.1] Id:2###### 执行SQL的主机信息# Query_time: 6.009129 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0###### SQL的执行信息SET timestamp=1631670667;###### SQL执行时间select sleep(6);###### SQL内容[root@linux-141 mysql-5.7.28]#
属性解释
# Time: -09-15T01:51:07.737834Z ###### 执行SQL时间# User@Host: root[root] @ [192.168.36.1] Id:2###### 执行SQL的主机信息# Query_time: 6.009129 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0###### SQL的执行信息SET timestamp=1631670667;###### SQL执行时间select sleep(6);###### SQL内容
3.2 调优工具 show profile
tips:
show profile,它也是调优工具
也是 MySQL 服务自带的分析调优工具
不过这款更高级
比较接近底层硬件参数的调优。
简介:
show profile 是 MySQL 服务自带更高级的分析调优工具
比较接近底层硬件参数的调优
1、查看 show profile 设置
-- 默认关闭,保存近15次的运行结果show variables like 'profiling%';
通过上面我们发现,show profile 工具默认是关闭状态,15 表示保存了近 15 次的运行结果。
2、开启调优工具
执行下面的命令开启
SET profiling = ON;
再次查看状态
show variables like 'profiling%';
3、查看最近 15 次的运行结果
-- 查看最近15次的运行结果show profiles;-- 可以显示警告和报错的信息show warnings;-- 慢查询语句SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
显示最近 15 次的运行结果
4、诊断运行的 SQL
接下来,我们一起诊断一下 query id 为 23 的慢查询
-- 语法SHOW PROFILE cpu,block io FOR QUERY query id;-- 示例SHOW PROFILE cpu,block io FOR QUERY 129;
开始执行
解释:通过Status一列,可以看到整条SQL的运行过程1. starting //开始2. checking permissions //检查权限3. Opening tables //打开数据表4. init //初始化5. System lock //锁机制6. optimizing //优化器7. statistics //分析语法树8. prepareing //预准备9. executing //引擎执行开始10. end //引擎执行结束11. query end //查询结束12. closing tables //释放数据表13. freeing items //释放内存14. cleaning up //彻底清理
查看类型选项SHOW PROFILE...后面的列,即:SHOW PROFILE ALL, BLOCK IO, ... FOR QUERY 209;ALL //显示索引的开销信息BLOCK IO //显示块IO相关开销CONTEXT SWITCHES //上下文切换相关开销CPU //显示CPU相关开销信息IPC //显示发送和接收相关开销信息MEMORY //显示内存相关开销信息PAGE FAULTS //显示页面错误相关开销信息SOURCE //显示和source_function,source_file,source_line相关的开销信息SWAPS //显示交换次数相关开销的信息
重要提示
如出现以下一种或者几种情况,说明SQL执行性能极其低下,亟需优化* converting HEAP to MyISAM //查询结果太大,内存都不够用了往磁盘上搬了* Creating tmp table //创建临时表:拷贝数据到临时表,用完再删* Copying to tmp table on disk //把内存中临时表复制到磁盘,危险* locked //出现死锁
4 如何定位不合理的 SQL
引言在应用的开发过程中,由于初期数据量小,开发人员写 SQL 语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化,本章将详细介绍在MySQL中优化SQL语句的方法。当面对一个有SQL性能问题的数据库时,我们应该从何处入手来进行系统的分析,使得能够尽快定位问题SQL并尽快解决问题。
4.1 如何查看 SQL 执行频率
MySQL 客户端连接成功后,通过
-- 服务器状态信息show [session|global] status;
命令可以提供服务器状态信息。show [session|global] status 可以根据需要加上参数 “session” 或者 “global” 来显示 session 级(当前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果。
如果不写,默认使用参数是 “session”。
下面的命令显示了当前 session 中所有统计参数的值:
show status like 'Com_______';
show status like 'Innodb_rows_%';
Com_xxx 表示每个 xxx 语句执行的次数,我们通常比较关心的是以下几个统计参数。
Com_*** : 这些参数对于所有存储引擎的表操作都会进行累计。
Innodb_*** : 这几个参数只是针对 InnoDB 存储引擎的,累加的算法也略有不同。
4.2 如何定位低效率 SQL
以下两种方式:
慢查询日志(重要) :通过慢查询日志定位那些执行效率较低的 SQL 语句,用 --log-slow-queries [=file_name] 选项启动时,mysqld 写一个包含所有执行时间超过 long_query_time 秒的 SQL 语句的日志文件。
tips:
关于慢查询 SQL 如何获取
参看上个章节
show processlist (重要) :
慢查询日志在查询结束以后才记录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题。
可以使用 show processlist 命令查看当前 MySQL 在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化。
属性字段解释1) id列,用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看2) user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句3) host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户4) db列,显示这个进程目前连接的是哪个数据库5) command列,显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等6) time列,显示这个状态持续的时间,单位是秒7) state列,显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成8) info列,显示这个sql语句,是判断问题语句的一个重要依据
4.3 使用 explain 分析执行计划
-- explain 分析执行计划explain SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
4.3.1 环境准备
CREATE TABLE `t_role` (`id` varchar(32) NOT NULL,`role_name` varchar(255) DEFAULT NULL,`role_code` varchar(255) DEFAULT NULL,`description` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `unique_role_name` (`role_name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `t_user` (`id` varchar(32) NOT NULL,`username` varchar(45) NOT NULL,`password` varchar(96) NOT NULL,`name` varchar(45) NOT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;CREATE TABLE `user_role` (`id` int(11) NOT NULL auto_increment ,`user_id` varchar(32) DEFAULT NULL,`role_id` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`),KEY `idx_role_user` (`role_id`,`user_id`),) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into `t_user` (`id`, `username`, `password`, `name`) values('1','super','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','超级管理员');insert into `t_user` (`id`, `username`, `password`, `name`) values('2','admin','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','系统管理员');insert into `t_user` (`id`, `username`, `password`, `name`) values('3','itcast','$2a$10$8qmaHgUFUAmPR5pOuWhYWOr291WJYjHelUlYn07k5ELF8ZCrW0Cui','test02');insert into `t_user` (`id`, `username`, `password`, `name`) values('4','stu1','$2a$10$pLtt2KDAFpwTWLjNsmTEi.oU1yOZyIn9XkziK/y/spH5rftCpUMZa','学生1');insert into `t_user` (`id`, `username`, `password`, `name`) values('5','stu2','$2a$10$nxPKkYSez7uz2YQYUnwhR.z57km3yqKn3Hr/p1FR6ZKgc18u.Tvqm','学生2');insert into `t_user` (`id`, `username`, `password`, `name`) values('6','t1','$2a$10$TJ4TmCdK.X4wv/tCqHW14.w70U3CC33CeVncD3SLmyMXMknstqKRe','老师1');INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('5','学生','student','学生');INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('7','老师','teacher','老师');INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('8','教学管理员','teachmanager','教学管理员');INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('9','管理员','admin','管理员');INSERT INTO `t_role` (`id`, `role_name`, `role_code`, `description`) VALUES('10','超级管理员','super','超级管理员');INSERT INTO user_role(id,user_id,role_id) VALUES(NULL, '1', '5'),(NULL, '1', '7'),(NULL, '2', '8'),(NULL, '3', '9'),(NULL, '4', '8'),(NULL, '5', '10') ;
4.3.2 explain 之 id
id 字段是 select 查询的序列号,是一组数字,表示的是查询中执行 select 子句或者是操作表的顺序。
id 情况有三种 :
1) id 相同表示加载表的顺序是从上到下。
explain select * from t_role r, t_user u, user_role ur where r.id = ur.role_id and u.id = ur.user_id ;
2) id 不同 id 值越大,优先级越高,越先被执行。
EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'))
3) id 有相同,也有不同,同时存在。id 相同的可以认为是一组,从上往下顺序执行;在所有的组中,id 的值越大,优先级越高,越先执行。
EXPLAIN SELECT * FROM t_role r , (SELECT * FROM user_role ur WHERE ur.`user_id` = '2') a WHERE r.id = (select role.id from t_user, user_role role where role.id = 10) ;
4.3.3 explain 之 select_type
表示 SELECT 的类型,常见的取值,如下表所示:
EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'));
4.3.4 explain 之 table
展示这一行的数据是关于哪一张表的
EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'));
4.3.5 explain 之 type
EXPLAIN SELECT * FROM t_role WHERE id = (SELECT role_id FROM user_role WHERE user_id = (SELECT id FROM t_user WHERE username = 'stu1'));
type 显示的是访问类型,是较为重要的一个指标,可取值为:
结果值从最好到最坏依次是:
NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALLsystem > const > eq_ref > ref > range > index > ALL
一般来说, 我们需要保证查询至少达到 range 级别, 最好达到 ref 。
4.3.6 explain 之 key
possible_keys : 显示可能应用在这张表的索引, 一个或多个。 key : 实际使用的索引, 如果为NULL, 则没有使用索引。key_len : 表示索引中使用的字节数。len=3*n+2(n为索引字段的长度)EXPLAIN select * from t_role where role_name = '超级管理员'; select 255 * 3 + 2; -- role_name VARCHAR(255)
4.3.7 explain 之 rows
扫描行的数量。
4.3.8 explain 之 extra
其他的额外的执行计划信息,在该列展示 。
EXPLAIN select u.username from t_user u order by u.username desc;
5 如何合理使用索引加速
tips:
500 万条建表 sql 参照网盘 sql 脚本
[root@linux-141 bin]# ./mysql -u root -p itcast < product_list-5072825.sql
索引是数据库优化最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的 MySQL 的性能优化问题。
5.1 验证索引提升查询效率
在我们准备的表结构 product_list 中, 一共存储了 500 多万记录;
mysql> select count(1) from product_list;+----------+| count(1) |+----------+| 5072825 |+----------+1 row in set (1.71 sec)mysql>
1) 根据 ID 查询
SELECT * FROM product_list WHERE id = 121926;
查询速度很快, 接近 0s , 主要的原因是因为 id 为主键, 有索引;
2). 根据 store_name 进行精确查询
执行用时 4 分钟
SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
查看 SQL 语句的执行计划 :
explain SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
处理方案 , 针对 store_name 字段, 创建索引 :
create index product_list_stname on product_list(store_name);
索引创建完成之后,再次进行查询 :
SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
通过 explain , 查看执行计划,执行 SQL 时使用了刚才创建的索引
-- 查看SQL语句的执行计划explain SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
5.2 索引的使用
5.2.1 准备环境
create table `tb_seller` (`sellerid` varchar (100),`name` varchar (100) not null,`nickname` varchar (50),`password` varchar (60),`status` varchar (1) not null,`address` varchar (100) not null,`createtime` datetime,primary key(`sellerid`))engine=innodb default charset=utf8; insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');create index idx_seller_name_sta_addr on tb_seller(name,status,address);
5.2.2 避免索引失效
组合索引 (name,status,address)
1) 全值匹配
对索引中所有列都指定具体值。
-- 全值匹配explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';ken_len = 3 * N + 2;-- name varchar(100) ==302-- status varchar(1) ==5-- address varchar(100) ==302
2) 最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
匹配最左前缀法则,走索引:
explain select * from tb_seller where name='小米科技';
违反最左前缀法则 , 索引失效:
explain select * from tb_seller where status='1';explain select * from tb_seller where status='1' and address='北京市';
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:
explain select * from tb_seller where name='小米科技' and address='北京市';
3) 范围查询右边的列
-- 使用范围查询的情况,右边的列失效 explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';explain select * from tb_seller where name='小米科技' and status>'1' and address='北京市';
根据前面的两个字段 name , status 查询是走索引的, 但是最后一个条件 address 没有用到索引。
4) 禁止列运算
-- 不要在索引列上进行运算操作, 索引将失效。explain select * from tb_seller where substring(name,3,2) ='科技';
5) 字符串不加单引号
造成索引失效。
-- 字符串不加单引号,造成索引失效。explain select * from tb_seller where name='科技' and status='0';explain select * from tb_seller where name='科技' and status=0;
由于,在查询时,没有对字符串加单引号,MySQL 的查询优化器,会自动的进行类型转换,造成索引失效。
【建议收藏】15755 字 讲透 MySQL 性能优化(包含 MySQL 架构 存储引擎 调优工具 SQL 索引 建议等等)
如果觉得《【建议收藏】15755 字 讲透 MySQL 性能优化(包含 MySQL 架构 存储引擎 调优》对你有帮助,请点赞、收藏,并留下你的观点哦!