MySQL监控与慢SQL解决思路
Posted 我只吃大碗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL监控与慢SQL解决思路相关的知识,希望对你有一定的参考价值。
实时监控语句耗时与SQL状态
使用语句show full processlist; 此命令有权限,自己的账号只能看自己的连接,root账号可以看全部连接。
字段 值 说明
Id 整数 连接和会话的唯一ID
User 字符串 哪个用户使用了此连接或者会话
Host 字符串 由那个ip的客户端连接到服务的
db 字符串 数据库名称
Command 字符串 连接执行命令状态,例如:一般就是休眠(sleep),查询(query),连接(connect)
Time 整数 空闲或者执行语句时间,如果是空闲,这个时间代表空闲了多久。如果是执行,那就是这个sql执行所经过的时间(sql执行的太久属于不正常现象)
State 字符串 详细见下方state字段描述
Info 字符串 如果是执行就显示正在执行的sql语句。因为长度有限,所以长的sql语句就显示不全,但是一个判断问题语句的重要依据。
state字段描述
显示使用当前连接的sql语句的状态,很重要的列,可用来判断mysql的运行状态。 这个命令中最关键的就是state列,mysql列出的状态主要有以下几种:
Checking table
正在检查数据表(这是自动的)。
Closing tables
正在将表中修改的数据刷新到磁盘中,同时正在关闭已经用完的表。这是一个很快的操作,如果不是这样的话,就应该确认磁盘空间是否已经满了或者磁盘是否正处于重负中。
Connect Out
复制从服务器正在连接主服务器。
Copying to tmp table on disk
由于临时结果集大于tmp_table_size,正在将临时表从内存存储转为磁盘存储以此节省内存。
Creating tmp table
正在创建临时表以存放部分查询结果。
deleting from main table
服务器正在执行多表删除中的第一部分,刚删除第一个表。
deleting from reference tables
服务器正在执行多表删除中的第二部分,正在删除其他表的记录。
Flushing tables
正在执行FLUSH TABLES,等待其他线程关闭数据表。
Killed
发送了一个kill请求给某线程,那么这个线程将会检查kill标志位,同时会放弃下一个kill请求。MySQL会在每次的主循环中检查kill标志位,不过有些情况下该线程可能会过一小段才能死掉。如果该线程程被其他线程锁住了,那么kill请求会在锁释放时马上生效。
Locked
被其他查询锁住了。
Sending data
正在处理Select查询的记录,同时正在把结果发送给客户端。
Sorting for group
正在为GROUP BY做排序。
Sorting for order
正在为ORDER BY做排序。
Opening tables
这个过程应该会很快,除非受到其他因素的干扰。例如,在执Alter TABLE或LOCK TABLE语句行完以前,数据表无法被其他线程打开。正尝试打开一个表。
Removing duplicates
正在执行一个Select DISTINCT方式的查询,但是MySQL无法在前一个阶段优化掉那些重复的记录。因此,MySQL需要再次去掉重复的记录,然后再把结果发送给客户端。
Reopen table
获得了对一个表的锁,但是必须在表结构修改之后才能获得这个锁。已经释放锁,关闭数据表,正尝试重新打开数据表。
Repair by sorting
修复指令正在排序以创建索引。
Repair with keycache
修复指令正在利用索引缓存一个一个地创建新索引。它会比Repair by sorting慢些。
Searching rows for update
正在讲符合条件的记录找出来以备更新。它必须在Update要修改相关的记录之前就完成了。
Sleeping
正在等待客户端发送新请求.
System lock
正在等待取得一个外部的系统锁。如果当前没有运行多个mysqld服务器同时请求同一个表,那么可以通过增加--skip-external-locking参数来禁止外部系统锁。
Upgrading lock Insert DELAYED
正在尝试取得一个锁表以插入新记录。
Updating
正在搜索匹配的记录,并且修改它们。
User Lock
正在等待GET_LOCK()。
Waiting for tables
该线程得到通知,数据表结构已经被修改了,需要重新打开数据表以取得新的结构。然后,为了能的重新打开数据表,必须等到所有其他线程关闭这个表。以下几种情况下会产生这个通知:FLUSH TABLES tbl_name, Alter TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE,或OPTIMIZE TABLE。 waiting for handler insert
Insert DELAYED
已经处理完了所有待处理的插入操作,正在等待新的请求。 大部分状态对应很快的操作,只要有一个线程保持同一个状态好几秒钟,那么可能是有问题发生了,需要检查一下。
杀死连接
语法为kill id; 例:kill 62532 使用方式是使用show full processlist 人工判断出锁表的会话,使用kill杀死此会话。不只是锁表的需要杀死,可能某些建立索引的,或者特别耗时的sql也需要杀死,原则就是无法很快处理掉的会话都需要杀死,如果不杀死可能会严重拖慢mysql的效率而造成死机,更多的慢sql等等,
记录导出慢SQL日志
通过
查看是否开启了慢sql日志以及日志位置。 配置my.ini文件(Linux下文件名为my.cnf), 查找到[mysqld]区段,增加日志的配置,如下示例:
[mysqld]
slow_query_log = 1; #开启
slow_query_log_file=/var/lib/mysql/slow_query.log #慢日志地址,缺省文件名host_name-slow.log
long_query_time=5; #运行时间超过该值的SQL会被记录,默认值>10,单位s(秒)
log_output=FILE
Linux下这些配置项应该已经存在,只是被注释掉了,可以去掉注释。 未使用索引的查询被记录到慢查询日志中。如果调优的话,建议开启这个选项。如果开启了这个参数,full index scan的sql也会被记录到慢查询日志中。
show variables like \'%log_queries_not_using_indexes%\'
set global log_queries_not_using_indexes=1
分析慢查询日志
mysqldumpslow 慢日志分析工具。 命令: -s 按照那种方式排序 c:访问计数 l:锁定时间 r:返回记录 al:平均锁定时间 ar:平均访问记录数 at:平均查询时间 -t 是top n的意思,返回多少条数据。 -g 可以跟上正则匹配模式,大小写不敏感。 得到返回记录最多的20个sql:
mysqldumpslow -s r -t 20 sqlslow.log
得到平均访问次数最多的20条sql:
mysqldumpslow -s ar -t 20 sqlslow.log
得到平均访问次数最多,并且里面含有ttt字符的20条sql:
mysqldumpslow -s ar -t 20 -g "ttt" sqldlow.log
注: 如果出现如下错误,Died at /usr/bin/mysqldumpslow line 161, <> chunk 405659.说明你要分析的sql日志太大了,请拆分后再分析 拆分的命令为:
tail -100000 mysql-slow.log>mysql-slow.20180725.log
pt-query-digest,为另外一款慢sql分析工具,也推荐使用。 Mysqlsla,功能最全面的慢sql分析工具。
执行计划与慢SQL分析
Explain分析 Explain是Mysql的自带查询优化器,负责select语句的优化器模块,可以模拟优化器执行SQL查询语句,从而知道Mysql是如何处理SQL的,语法也很简单:Explain + SQL。 以下是通过explain查询出的几个属性
(常见性能瓶颈 —— CPU:CPU饱和一般发生在数据装入内存或从磁盘上读取数据时 IO:磁盘I/O瓶颈发生在装入数据远大于内存容量时 服务器硬件的性能瓶颈:top,free,iostat,vmstat来查看系统的性能状态) 用途: (1)表的读取顺序,id (2)数据读取操作的操作类型,select_type (3)哪些索引可以使用 (4)哪些索引被实际使用 (5)表之间的引用 (6)每张表有多少行被优化器查询 rows
id:反映的是表的读取的顺序,或查询中执行select子句的顺序。 小表永远驱动大表,三种情况: (1)id相同,执行顺序是由上至下的 (2)id不同,如果是子查询,id序号会递增,id值越大优先级越高,越先被执行 (3)id存在相同的,也存在不同的,所有组中,id越大越先执行,如果id相同的,从上往下顺序执行
derived是衍生虚表的意思,derived2中的2对应id2
select_type:反映的是Mysql理解的查询类型 (1)simple:简单的select查询,查询中不包含子查询或union。 (2)primary:查询中若包含任何复杂的字部分,最外层查询标记为primary。 (3)subquery:select或where列表中的子查询。 (4)derived(衍生):在from列表中包含的子查询,Mysql会递归执行这些子查询,把结果放在临时表里。 (5)union:若第二个select出现在union后,则被标记为union,若union包含在from字句的子查询中,外层select将被标记为derived (6)union result:union后的结果集
table:反映这一行数据是关于哪张表的
type:访问类型排序反映sql优化的状态,至少达到range级别,最好能达到ref查询效率:system > const > eq_ref > ref > range > index > all (完整的排序:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >all) (1)system:从单表只查出一行记录(等于系统表),这是const类型的特例,一般不会出现 (2)const:查询条件用到了常量,通过索引一次就找到,常在使用primary key或unique索引中出现。
where id=1写死,所以类型是const (3)eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。 (4)ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它可能会找到多个符合条件的行,与eq_ref的差别是eq_ref只匹配了一条记录。 (5)range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般是在where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。与eq_ref和ref的区别在于筛选条件不是固定值,是范围。
(6)index:full Index scan,index和all的区别为index类型只遍历索引树。这通常比all快,因为索引文件通常比数据文件小。
要获得的id信息,刚好id在索引上,从索引中读取(all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的) (7)all:全表扫描,如果查询数据量很大时,全表扫描效率是很低的。
possible_keys、key、key_len:反映实际用到了哪个索引,索引是否失效 (1)possible_keys:Mysql推测可能用到的索引有哪些,但不一定被查询实际使用 (2)key:实际使用的索引,若为null,则可能没建索引或索引失效。
(查询中若使用了覆盖索引,则该索引仅出现在key列表中。 覆盖索引:select后面的字段和所建索引的个数、顺序一致) (3)key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。同样的查询结果下,长度越短越好。 key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
ref:反映哪些列或常量被用于查找索引列上的值
rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
仅通过主键索引查找是641行
-
建完相关的复合索引再查,需要查询的行数就变少了
-
Extra (1)using filesort:mysql中无法利用索引完成的排序,这时会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。
创建索引时就会对数据先进行排序,出现using filesort一般是因为order by后的条件导致索引失效,最好进行优化。
order by的排序最好和所建索引的顺序和个数一致 (2)using temporary:使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
影响更大,所以要么不建索引,要么group by的顺序要和索引一致
(3)using index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率好 覆盖索引:select后的数据列只从索引就能取得,不必读取数据行,且与所建索引的个数(查询列小于等于索引个数)、顺序一致。 所以如果要用覆盖索引,就要注意select的列只取需要用到的列,不用select *,同时如果将所有字段一起做索引会导致索引文件过大,性能会下降。
出现using where,表明索引被用来执行索引键值的查找
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。 (4)using where:表明使用了where过滤 (5)using join buffer:使用了连接缓存 (6)impossible where:where子句的值是false (7)select tables optimized away (8)distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作
Show Profile分析
Show Profile也是分析慢SQL的一种手段,但它能获得比explain更详细的信息,能分析当前会话中语句执行的资源消耗情况,能获得这条SQL在整个生命周期的耗时,相当于执行时间的清单,也很重要。 默认关闭。开启后,会在后台保存最近15次的运行结果,然后通过Show Profile命令查看结果。
开启:set profiling = on;
查看:SHOW VARIABLES LIKE \'profiling%\';
通过Show Profile能查看SQL的耗时
通过Query_ID可以得到具体SQL从连接 - 服务 - 引擎 - 存储四层结构完整生命周期的耗时
可用参数type:
ALL #显示所有的开销信息
BLOCK IO #显示块IO相关开销
CONTEXT SWITCHES #上下文切换相关开销
CPU #显示CPU相关开销信息
IPC #显示发送和接收相关开销信息
MEMORY #显示内存相关开销信息
PAGE FAULTS #显示页面错误相关开销信息
SOURCE #显示和Source_function,Source_file,Source_line相关的开销信息
SWAPS #显示交换次数相关开销的信息
出现这四个status时说明有问题,group by可能会创建临时表
- 危险状态:
-
converting HEAP to MyISAM #查询结果太大,内存不够用了,在往磁盘上搬
Creating tmp table #创建了临时表,回先把数据拷贝到临时表,用完后再删除临时表
Copying to tmp table on disk #把内存中临时表复制到磁盘,危险!!!
locked
二进制日志解析
什么是二进制日志?
用来记录操作MySQL数据库中的写入性操作(增删改,但不包括查询),相当于sqlserver中的完整恢复模式下的事务日志文件。
二进制日志的作用?
用于复制,配置了主从复制的时候,主服务器会将其产生的二进制日志发送到slave端,slave端会利用这个二进制日志的信息在本地重做,实现主从同步
用户恢复,MySQL可以在全备和差异备份的基础上,利用二进制日志进行基于时间点或者事物Id的恢复操作。原理同于主从复制的日志重做。
可以用作数据分发,有收费的工具可以把一个数据库的数据分发到其他的mysql数据库,在事务性不强的统计等其他方面的需求可以使用分发库,请注意分发库会有一定的延迟(正常延迟是毫秒级)。
Mysqlbinlog命令行工具可以对二进制日志进行操作。二进制日志很重要,请务必开启。
MySQL的读写分离
Mysql的读写分离正常情况下只支持一主多从。
读写分离应用中间件来支持(尽量不要使用多数据源的方式):
可以支持SQLhint路由,可以根据规则引入确认的一个节点上
可以把事务全部到主节点上(如果不这样做读在从节点上,写在主节点上会出现脏数据,因为主从同步是有少许延迟的,虽然是毫秒级的)
不再详细描述
索引的注意事项
索引对查询是速度提升以及减低慢SQL的查询是非常必要的,但是索引建立的复杂度高的,会影响插入和更新的效率,但是不要为了插入和更新的效率而不建立索引或者少建立索引,因为插入和更新在没有表锁的情况下基本没有慢SQL。
但是如果一定想要屏蔽插入和更新因为索引带来的效率下降有以下方式(中间件方式): 分库分表,减低单表数据量(数据量少了索引也就小了),这样因为插入和更新造成的索引更新效率就变的影响非常的小。
读写分离,主库(主要负责插入和更新)减少或者弱化索引的数量,从库(主要负责读取)增加索引的组合方式和数量,从库还可以是多个,不同的从库在同一个表建立的索引可以不一样,这样可以通过路由的方式把不同规则的语句路由到不同的从库上。
例如: 数据库DB,有2个只读节点DBR1和DBR2。对table中有A、B、C三个列,对这3个列进行的索引。正常来说数据库table表中如果是AB一个组合索引,ABC一个组合索引,BC一个组合索引就会让索引变的比较臃肿,并且SQL在解析的时候还有可能用错索引。 DBR1只建立ABC索引,DBR2建立AB索引,就变的比较清晰了。
建立索引不要太在意网上的那些方式,包括 in 语句无法用到索引等,要相信执行计划(explain)而不要相信直觉,因为数据库不断的升级,一个版本都会有部分的差异。
建立组合索引的原则可以基本的理解为, where 条件后有几个and条件,这些条件就应该建立一个组合索引,有些and条件是可变的或者拼接的,所以索引不要这样盲目建立,需要根据慢sql来建立。 高并发正在运行的生成系统建立索引需要注意的步骤: 1、打开查询编辑器输入,show full processlist 2、打了另外的编辑器输入建立索引的语句 3、执行建立索引的语句 4、切换第一个查询编辑器不断的运行show full processlist,查看目前所有会话执行sql的状态,如果出现大量的缓慢SQL现象以及死锁现象,尽快用kill语句杀死一直在运行的会话(不只是这个建立索引的会话)。因为失败,建立索引需要再次规划时间,正常情况下应该是凌晨进行。
可以使用函数等方式强制只用某个索引或者取消使用索引:
SELECT * FROM TABLE1 FORCE INDEX (FIELD1) …
SELECT * FROM TABLE1 IGNORE INDEX (FIELD1, FIELD2) …
数据库设计范式
第一范式
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库满足第一范式。 第一范式的合理遵循需要根据系统给的实际需求来确定。比如某些数据库系统中需要用到"地址"这个属性,本来直接将"地址"属性设计成为一个数据库表的字段就行,但是如果系统经常访问"地址"属性中的"城市"部分,那么一定要把"地址"这个属性重新拆分为省份、城市、详细地址等多个部分来进行存储,这样对地址中某一个部分操作的时候将非常方便,这样设计才算满足数据库的第一范式。如下图。
上图所示的用户信息遵循第一范式的要求,这样对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。
第二范式
第二范式在第一范式的基础上更进一层,第二范式需要确保数据库表中每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。 比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下图。
这里产生一个问题:这个表中是以订单编号和商品编号作为联合主键,这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品的编号相关,所以在这里违反了第二范式的设计原则。 而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了,如下图。
这里这样设计,在很大程度上减小了数据库的冗余,如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。
第三范式
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。 比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系,而不可以在订单表中添加关于客户其他信息(比如姓名、所属公司)的字段,如下面这两个表所示的设计就是一个满足第三范式的数据库表。
这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必再订单信息表中多次输入客户信息的内容,减小了数据冗余。
以上是关于MySQL监控与慢SQL解决思路的主要内容,如果未能解决你的问题,请参考以下文章