1)MySQL总体架构介绍
成都创新互联是一家专业提供天峨企业网站建设,专注与做网站、成都做网站、H5页面制作、小程序制作等业务。10年已为天峨众多企业、政府机构等服务。创新互联专业的建站公司优惠进行中。
2)MySQL存储引擎调优
3)常用慢查询分析工具
4)如何定位不合理的SQL
5)SQL优化的一些建议
引言
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 认为最优的方案去执行。
执行器: 执行语句,然后从存储引擎返回数据。
引言
和大多数的数据库不同, 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%';
CREATE TABLE t1(
id INT ,
name VARCHAR(20)
) ENGINE = MyISAM;
ALTER TABLE t1 ENGINE = InnoDB;
常见的存储引擎 :
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 mysql 98304 9月 14 15:50 t_account_innodb.ibd
[root@linux-141 itcast]#
MEMORY存储引擎 : 内存存储 , 速度快 ,不安全 ,适合小量快速访问的数据。表结构保存在.frm中。
特性对比 :
特点 | InnoDB | MyISAM | MEMORY | MERGE | NDB |
---|---|---|---|---|---|
存储限制 | 64TB | 有 | 有 | 没有 | 有 |
事务安全 | 支持 | ||||
锁机制 | 行锁(适合高并发) | 表锁 | 表锁 | 表锁 | 行锁 |
B树索引 | 支持 | 支持 | 支持 | 支持 | 支持 |
哈希索引 | 支持 | ||||
全文索引 | 支持(5.6版本之后) | 支持 | |||
集群索引 | 支持 | ||||
数据索引 | 支持 | 支持 | 支持 | ||
索引缓存 | 支持 | 支持 | 支持 | 支持 | 支持 |
数据可压缩 | 支持 | ||||
空间使用 | 高 | 低 | N/A | 低 | 低 |
内存使用 | 高 | 低 | 中等 | 低 | 高 |
批量插入速度 | 低 | 高 | 高 | 高 | 高 |
支持外键 | 支持 |
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
以下是几种常用的存储引擎的使用环境。
tips
硬件(cpu、内存等)相关
了解即可
关于提升硬件设备性能:
例如选择尽量高频率的内存(频率不能高于主板的支持)、提升网络带宽、使用SSD高速磁盘、提升CPU性能等。
CPU的选择:
磁盘的选择
影响数据库最大的性能问题就是磁盘I/O
为提高数据库的IOPS性能,可使用SSD或PCIE-SSD高速磁盘设备
磁盘IO的优化
可以用RAID来进行优化
常用RAID(磁盘阵列)级别:
RAID0:也称为条带,就是把多个磁盘链接成一个硬盘使用,这个级别IO最好
RAID1:也称为镜像,要求至少有两个磁盘,每组磁盘存储的数据相同
RAID5:也是把多个(最少3个)硬盘合并成一个逻辑盘使用,数据读写时会建立奇偶校验信息,并且奇偶校验信息和相对应的数据分别存储在不同的磁盘上。当RAID5的一个磁盘数据发生损坏后,利用剩下的数据和响应的奇偶校验信息去恢复被损坏的数据
RAID1+0(建议使用):就是RAID0和RAID1的组合。同时具备两个级别的优缺点,一般建议数据库使用这个级别。
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文件就会产生很大的文件,建议关闭
tips:
在MySQL 8.0之后废弃这个功能
原理:复杂、实用性不高
作为了解即可
开启Mysql的查询缓存,当执行完全相同的SQL语句的时候,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存。
回顾
查看当前的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%';
各个变量的含义如下:
参数 | 含义 |
---|---|
Qcache_free_blocks | 查询缓存中的可用内存块数 |
Qcache_free_memory | 查询缓存的可用内存量 |
Qcache_hits | 查询缓存命中数 |
Qcache_inserts | 添加到查询缓存的查询数 |
Qcache_lowmen_prunes | 由于内存不足而从查询缓存中删除的查询数 |
Qcache_not_cached | 非缓存查询的数量(由于 query_cache_type 设置而无法缓存或未缓存) |
Qcache_queries_in_cache | 查询缓存中注册的查询数 |
Qcache_total_blocks | 查询缓存中的块总数 |
MySQL的查询缓存默认是关闭的,需要手动配置参数 query_cache_type , 来开启查询缓存。query_cache_type 该参数的可取值有三个 :
值 | 含义 |
---|---|
OFF 或 0 | 查询缓存功能关闭 |
ON 或 1 | 查询缓存功能打开,SELECT的结果符合缓存条件即会缓存,否则,不予缓存,显式指定 SQL_NO_CACHE,不予缓存 |
DEMAND 或 2 | 查询缓存功能按需进行,显式指定 SQL_CACHE 的SELECT语句才会缓存;其它均不予缓存 |
在 my.cnf 配置中,增加以下配置 :
#开启查询缓存
query_cache_type=1
配置完毕之后,重启服务既可生效 ;
然后就可以在命令行执行SQL语句进行验证 ,执行一条比较耗时的SQL语句,然后再多执行几次,查看后面几次的执行时间;获取通过查看查询缓存的缓存命中数,来判定是否走查询缓存。
-- 执行SQL语句进行验证 查询缓存
SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
-- 将SELECT修改为小写,发现缓存失效
SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
可以在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;
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/my.cnf
[root@linux-141 itcast]# service mysql restart
Shutting down MySQL.... SUCCESS!
Starting MySQL. SUCCESS!
1) 将尽量多的内存分配给MySQL做缓存,但要给操作系统和其他程序预留足够内存。
2) MyISAM 存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此,如果有MyISAM表,就要预留更多的内存给操作系统做IO缓存。
3) 排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认值的设置要根据最大连接数合理分配,如果设置太大,不但浪费资源,而且在并发连接较高时会导致物理内存耗尽。
MyISAM 存储引擎使用 key_buffer 缓存索引块,加速myisam索引的读写速度。对于myisam表的数据块,mysql没有特别的缓存机制,完全依赖于操作系统的IO缓存。
key_buffer_size决定MyISAM索引块缓存区的大小,直接影响到MyISAM表的存取效率。可以在MySQL参数文件中设置key_buffer_size的值,对于一般MyISAM数据库,建议至少将1/4可用内存分配给key_buffer_size。
在my.cnf 中做如下配置:
key_buffer_size=512M
如果需要经常顺序扫描MyISAM 表,可以通过增大read_buffer_size的值来改善性能。但需要注意的是read_buffer_size是每个session独占的,如果默认值设置太大,就会造成内存浪费。
对于需要做排序的MyISAM 表的查询,如带有order by子句的sql,适当增加 read_rnd_buffer_size 的值,可以改善此类的sql性能。
但需要注意的是 read_rnd_buffer_size 是每个session独占的,如果默认值设置太大,就会造成内存浪费。
innodb用一块内存区做IO缓存池,该缓存池不仅用来缓存innodb的索引块,而且也用来缓存innodb的数据块。
该变量决定了 innodb 存储引擎表数据和索引数据的最大缓存区大小。在保证操作系统及其他程序有足够内存可用的情况下,innodb_buffer_pool_size 的值越大,缓存命中率越高,访问InnoDB表需要的磁盘I/O 就越少,性能也就越高。
innodb_buffer_pool_size=512M
决定了innodb重做日志缓存的大小,对于可能产生大量更新记录的大事务,增加innodb_log_buffer_size的大小,可以避免innodb在事务提交前就执行不必要的日志写入磁盘操作。
innodb_log_buffer_size=10M
从实现上来说,MySQL Server 是多线程结构,包括后台线程和客户服务线程。多线程可以有效利用服务器资源,提高数据库的并发性能。在Mysql中,控制并发连接和线程的主要参数包括 max_connections、back_log、thread_cache_size、table_open_cahce。
最大可支持的连接数
采用max_connections 控制允许连接到MySQL数据库的最大数量,默认值是 151。如果状态变量 connection_errors_max_connections 不为零,并且一直增长,则说明不断有连接请求因数据库连接数已达到允许最大值而失败,这时可以考虑增大max_connections 的值。
Mysql 最大可支持的连接数,取决于很多因素,包括给定操作系统平台的线程库的质量、内存大小、每个连接的负荷、CPU的处理速度,期望的响应时间等。在Linux 平台下,性能好的服务器,支持 500-1000 个连接不是难事,需要根据服务器性能进行评估设定。
积压请求栈大小
back_log 参数控制MySQL监听TCP端口时设置的积压请求栈大小。如果MySql的连接数达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源,将会报错。5.6.6 版本之前默认值为 50 , 之后的版本默认为 50 + (max_connections / 5), 但最大不超过900。
如果需要数据库在较短的时间内处理大量连接请求, 可以考虑适当增大back_log 的值。
执行线程可打开表缓存个数
该参数用来控制所有SQL语句执行线程可打开表缓存的数量, 而在执行SQL语句时,每一个SQL执行线程至少要打开 1 个表缓存。该参数的值应该根据设置的最大连接数 max_connections 以及每个连接执行关联查询中涉及的表的最大数量来设定 :
max_connections x N ;
缓存客户服务线程的数量
为了加快连接数据库的速度,MySQL 会缓存一定数量的客户服务线程以备重用,通过参数 thread_cache_size 可控制 MySQL 缓存客户服务线程的数量。
innodb_lock_wait_timeout
事务等待行锁的时间
该参数是用来设置InnoDB 事务等待行锁的时间,默认值是50ms , 可以根据需要进行动态设置。对于需要快速反馈的业务系统来说,可以将行锁的等待时间调小,以避免事务长时间挂起; 对于后台运行的批量处理程序来说, 可以将行锁的等待时间调大, 以避免发生大的回滚操作。
引言
在日常的业务开发中
MySQL 出现慢查询是很常见的
大部分情况下会分为两种情况
1、业务增长太快
2、要么就是SQL 写的太xx了
所以
对慢查询 SQL 进行分析和优化很重要
其中 mysqldumpslow 是 MySQL 服务自带的一款很好的分析调优工具
1、什么是MySQL 慢查询日志
MySQL提供的一种慢查询日志记录,用来记录在MySQL查询中响应时间超过阀值的记录
具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中
2、如何查看慢查询设置情况
慢查询的时间阈值设置
show variables like '%slow_query_log%';
解释
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 my.cnf
在[mysqld]下添加:
slow_query_log = 1
slow_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 会记录到慢查询日志
-- 查看阀值(大于),默认10s
show variables like 'long_query_time%';
默认值是10秒
4)如何设置查询阀值
-- 设置慢查询阀值
set global long_query_time = 1;
备注:另外开一个session或重新连接 ,才会看到变化
执行成功发发现慢sql的时间变成了1秒
配置文件设置
vim my.cnf
[mysqld]
long_query_time = 1
log_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
语法格式
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.sock
Time Id Command Argument
# Time: 2021-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: 0
use 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.log
Reading mysql slow query log from /opt/mysql-5.7.28/data/linux-141-slow.log
Died at ./bin/mysqldumpslow line 167, <> chunk 28.
[root@linux-141 mysql-5.7.28]#
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.sock
Time Id Command Argument
# Time: 2021-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: 0
use itcast;
SET timestamp=1631670031;
-- 睡眠2s再执行
select sleep(2);
# Time: 2021-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: 0
SET timestamp=1631670632;
select sleep(3);
# Time: 2021-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: 0
SET timestamp=1631670655;
select sleep(4);
# Time: 2021-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: 0
SET timestamp=1631670661;
select sleep(5);
# Time: 2021-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: 2021-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内容
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 //出现死锁
引言
在应用的开发过程中,由于初期数据量小,开发人员写 SQL 语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化,本章将详细介绍在MySQL中优化SQL语句的方法。
当面对一个有SQL性能问题的数据库时,我们应该从何处入手来进行系统的分析,使得能够尽快定位问题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_select | 执行 select 操作的次数,一次查询只累加 1。 |
Com_insert | 执行 INSERT 操作的次数,对于批量插入的 INSERT 操作,只累加一次。 |
Com_update | 执行 UPDATE 操作的次数。 |
Com_delete | 执行 DELETE 操作的次数。 |
Innodb_rows_read | select 查询返回的行数。 |
Innodb_rows_inserted | 执行 INSERT 操作插入的行数。 |
Innodb_rows_updated | 执行 UPDATE 操作更新的行数。 |
Innodb_rows_deleted | 执行 DELETE 操作删除的行数。 |
Connections | 试图连接 MySQL 服务器的次数。 |
Uptime | 服务器工作时间。 |
Slow_queries | 慢查询的次数。 |
Com_*** : 这些参数对于所有存储引擎的表操作都会进行累计。
Innodb_*** : 这几个参数只是针对InnoDB 存储引擎的,累加的算法也略有不同。
以下两种方式:
慢查询日志(重要) : 通过慢查询日志定位那些执行效率较低的 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语句,是判断问题语句的一个重要依据
-- explain 分析执行计划
explain SELECT * FROM product_list WHERE store_name = '联想北达兴科专卖店';
字段 | 含义 |
---|---|
id | select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。 |
select_type | 表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等 |
table | 输出结果集的表 |
partitions | 匹配的分区 |
type | 表示表的连接类型,性能由好到差的连接类型为( system ---> const -----> eq_ref ------> ref -------> ref_or_null----> index_merge ---> index_subquery -----> range -----> index ------> all ) |
possible_keys | 表示查询时,可能使用的索引 |
key | 表示实际使用的索引 |
key_len | 索引字段的长度 |
rows | 扫描行的数量 |
filtered | 按表条件过滤的行百分比 |
extra | 执行情况的说明和描述 |
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') ;
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) ;
表示 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'));
select_type | 含义 |
---|---|
SIMPLE | 简单的select查询,查询中不包含子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子查询,最外层查询标记为该标识 |
SUBQUERY | 在SELECT 或 WHERE 列表中包含了子查询 |
DERIVED | 在FROM 列表中包含的子查询,被标记为 DERIVED(衍生) MYSQL会递归执行这些子查询,把结果放在临时表中 |
UNION | 若第二个SELECT出现在UNION之后,则标记为UNION ; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为 : DERIVED |
UNION RESULT | 从UNION表获取结果的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'));
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 显示的是访问类型,是较为重要的一个指标,可取值为:
type | 含义 |
---|---|
NULL | MySQL不访问任何表,索引,直接返回结果 |
system | 表只有一行记录(等于系统表),这是const类型的特例,一般不会出现 |
const | 表示通过索引一次就找到了,const 用于比较primary key 或者 unique 索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL 就能将该查询转换为一个常量。const会将 "主键" 或 "唯一" 索引的所有部分与常量值进行比较 |
eq_ref | 类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条。常见于主键或唯一索引扫描 |
ref | 非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行(多个) |
range | 只检索给定返回的行,使用一个索引来选择行。 where 之后出现 between , < , > , in 等操作。 |
index | index 与 ALL的区别为 index 类型只是遍历了索引树, 通常比ALL 快, ALL 是遍历数据文件。 |
all | 将遍历全表以找到匹配的行 |
结果值从最好到最坏依次是:
NULL > system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
system > const > eq_ref > ref > range > index > ALL
一般来说, 我们需要保证查询至少达到 range 级别, 最好达到ref 。
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)
扫描行的数量。
其他的额外的执行计划信息,在该列展示 。
EXPLAIN select u.username from t_user u order by u.username desc;
extra | 含义 |
---|---|
using filesort | 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取, 称为 “文件排序”, 效率低。 |
using temporary | 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于 order by 和 group by; 效率低 |
using index | 表示相应的select操作使用了覆盖索引, 避免访问表的数据行, 效率不错。 |
tips:
500万条建表sql参照网盘sql脚本
[root@linux-141 bin]# ./mysql -u root -p itcast < product_list-5072825.sql
索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题。
在我们准备的表结构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 = '联想北达兴科专卖店';
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_st
分享标题:【建议收藏】15755字,讲透MySQL性能优化(包含MySQL架构、存储引擎、调优工具、SQL、索引、建议等等)
当前地址:http://njwzjz.com/article/dsopcce.html