MySQL性能优化(慢查询日志、Explain执行计划、show profile、MySQL服务优化)
合理利用慢查询日志,获取所有查询时间比较长的sql语句
explain执行计划查询,定位部分问题
show profile查看SQL执行时的资源使用情况,定位问题
调整操作系统参数
升级服务器硬件
MySQL优化之慢查询日志
慢查询日志:用来记录查询时间超过某个设定值的SQL语句
-
至于多慢才算慢,这个不得同一而语,是项目与业务而定吧
-
查看慢查询日志状态,默认关闭:show variables like '%slow_query%';
-
开启的两种方式:
-
短期有效
-
set global slow_query_log = ON; (开启)
-
set global long_query_time = 1; (便于测试,设置阀值,后面我改的是配置文件为0.1)
-
-
长期有效,修改配置文件:my.cnf
-
如果是自定义的文件,一定要给权限,否则因为日志文件无操作权限,导致慢查询开启失败
-
最为霸道:chmod 777 ...
-
-
-
我们可以测试一个sql,来查看一下效果:select sleep(3);
-
Query_time:这条sql执行的时间
-
Lock_time:在Mysql服务器阶段等待表锁时间
-
Rows_sent:查询返回的行数
-
Rows_examined:查询检查的行数,越长查询的时间越长
-
最后一排就是我们查询的sql语句信息
开启慢查询,配置日志文件,配置阀值,重启服务
可通过:show status like 'slow_queries'; 统计慢查询的次数
去配置的日志文件中查看具体的信息,得到慢查询SQL语句
MySQL优化Explain
在上面的慢查询日志过程中,我们已经拿到了耗时较长的sql语句,接下来该对其SQL语句进行判断了
使用explain查看sql执行计划,用法:explain select * from test;
从上面的执行计划中,我们先来零基础的熟悉一下,各个指标的含义记忆改指标的值得含义
-
id:这个东西就是一个标识符,无需理会
-
select_type:表示查询中每个select子句的类型(简单 OR复杂)
-
simple:查询中不包含子查询或者union
-
primary:一般出现于复杂查询语句,比如包含子查询的sql就会被标识为primary
-
union:表示连接查询的第二个或者更后面的查询语句
-
dependent union:union中的第二个或后面的select语句,取决于外面的查询
-
union result: 连接查询的结果
-
subquery:子查询中的第一个select语句
-
dependent subquery:子查询中的第一个select语句,取决于外面的查询
-
derived: select (from子句的子查询)
-
-
table:表示查询的表
-
partitions:如果你的查询是基于分区表,显示查询将访问的分区
-
type:表的连接类型标识(最佳到最差递推)
-
system:表仅有一行,这是const类型的特列,平时不会出现,这个也可以忽略不计
-
const:数据表最多只有一个匹配行,所以很快,可理解为const是最优化的
-
eq_ref:用于使用 = 比较带索引的列
-
ref:查询条件索引既不是unique也不是primary key的情况,用于=或<或>操作符的带索引的列
-
ref_or_null:如同ref,但添加了mysql可以专门搜索包含null值的行。解决子查询中常用该联接类型
-
上面五个算是比较好的类型标识了,下面开始拉垮的类型标识介绍
-
index_merge:使用了索引合并优化方法
-
在这种情况下,key列包含了使用的索引清单
-
key_len包含了使用的索引的最长的关键元素
-
-
unique_subquery:该类型替换了下面形式的IN子查询的ref
-
value in(select primary_key from table where some_expr)
-
-
index_subquery:该联接类型类似于unique_subquery
-
可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引
-
value in (select key_column from table where some_expr)
-
-
range:只检索给定范围的行,使用一个索引来选择行
-
index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小
-
all :进行完整的表扫描。(性能最差)
-
-
possible keys:指出MySQL能使用哪个索引在该表中定位到数据
-
如果该列为null,说明没有使用索引,可以创建索引来提高性能
-
-
key:显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是 null
-
ken_len:显示MySQL确定使用的索引的长度。如果键是NULL,则长度为NULL
-
ref:显示使用哪个列或常数与key一起从表中选择行
-
rows:显示MySQL认为它执行查询时必须检查的行数
-
Extra:该列包含MySQL解决查询的详细信息
-
Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行
-
Not exists:能够对查询进行left join 优化,发现1个匹配left join标准的行后,不再为前面的的行组合在该表内检查更多的行
-
range checked for each record (index map: #)
-
MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用
-
-
Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行
-
Using index:从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息
-
Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果
-
Using where:where子句用于限制哪一个行匹配下一个表或发送到客户
-
Using sort_union(...), Using union(...), Using intersect(...):这些函数说明如何为index_merge联接类型合并索引扫描
-
Using index for group-by:类似于访问表的Using index方式,Using index for group-by表示MySQL发现了一个索引,可以用来查 询GROUP BY或DISTINCT查询的所有列,而不要额外搜索硬盘访问实际的表
-
MySQL优化show profile
Query Profile是MySQL自带的一种Query诊断分析工具,可以完整的显示一条sql执行的各方面的详细信息,默认关闭;
-
查看是否开启Profile功能
-
select @@profiling; 默认为0【关闭】
-
show variables like '%profil%'; 默认OFF【关闭】
-
-
开启Profile :set profiling=1;
-
然后我们测试两个sql来看看效果如何?
-
select count (*) from test3;
-
select count(id) from test3;
-
执行show profiles;
-
我们就已经看到count (列) 要比 count(*) 耗时要少一半多
-
-
得到两条SQL的Query_ID,我们根据Query_ID去查询详细的信息
-
我们想看的不是两个sql的数据对比,一次测试结果也不足以为为准,但是我们可以发现
-
刚开始建立连接staring的耗时,查询完毕发送数据Sending data在整个耗时中占比都是很高的
-
所以我们使用连接池
-
建议SQL语句之返回我们想要的数据,即可实现对这一前一后的耗时优化
-
主要还是分为一个大的模块:CPU、磁盘IO、数据访问、数据运算影响 ?
-
-
SQL语句优化(面向开发人员)
索引优化
-
为搜索字段、排序字段、select查询列,创建合适的索引,不过要考虑数据的业务厂家:查询多还是增删多
-
尽量建立组合索引并注意组合索引的创建顺序,按照顺序组织查询条件,尽量将筛选颗粒度大的条件放最左边
-
尽量使用覆盖索引,select 语句中尽量不要使用 *
-
order by 、group by 尽量要使用到索引
-
索引的长度尽量短,短索引可以节省索引空间,使得内存中可以装在更多的键值索引,太长的列可以选择建立前缀索引
-
索引更新不能太频繁,更新太频繁的数据不适合创建索引,因为维护索引的成本很大
-
order by 的索引生效,order by排序应该遵循最佳左前缀查询,如果是使用多个索引字段进行排序,那么排序的规则必然相同(同什同降),否则索引同样会失效
-
尽量避免索引失效,比如不正确的模糊查询,组合索引使用不遵循规则,or关键字,子查询建临时表耗费性能等
LIMIT优化
-
如果预计select命中的结果只有一条,那么可以使用limit 1 可以停止全表扫描
-
处理分页会使用limitm,当翻页刀到非常靠后的页面是,偏移量会非常大,这是limit的效率会非常低,
-
limit offset size:其实就是offset的问题,导致MySQL扫描大量不需要的行然后抛弃掉
-
解决方案:
-
单表分页时,使用自增主键排序,先使用where id > offset limit后面只写rows
-
select * from (select * from test where id > 1000000 and id < 1000500 order by id) t limit 0,20;
-
其他查询优化
-
小表驱动大表,比如使用left jion时,因为使用join的话,第一张表是必须全变扫描的,所以如果一少关联多久可以减少扫描次数
-
避免全表扫描,MySQL在使用不等于 != 或 <> 的时候是无法使用索引的会导致全表扫描
-
避免MySQL放弃索引查询
-
特殊情况,当数据量少(几万)的时候,全表扫描会比使用索引快,则可以不使用索引
-
-
尽量不要使用count(*),而是使用count(主键)
-
count(*):遍历所有的行、列,查询行数
-
count(列):查询指定列不为null的行数,只查询了指定列
-
-
join两张表的关联字段最好都建立索引
-
select * from user u left join order o on u.id = o.user_id;
-
-
where条件中尽量不要使用not in 语句,建议使用not exists
-
合理使用慢查询日志,explain执行计划查询。show profile查询SQL执行时的资源使用情况
服务器优化
关于服务器优化,我个人觉得应该分为两个部分
服务器硬件升级
加内存,升CPU,机械硬盘换SSD......(说个锤子)
MySQL配置文件(自身)优化
下面详细说明
通过优化MySQL的参数可以提高资源利用率,从而达到提高MySQL服务器性能的目的
服务器配置 : 4核 8G CentOS7
[client] port=3306 socket=/var/lib/mysql/mysql.sock default-character-set=utf8mb4 [mysqld] bind-address=0.0.0.0 port=3306 socket=/var/lib/mysql/mysql.sock datadir=/var/lib/mysql character-set-server=utf8mb4 ## innodb ######################################################################### default_storage_engine=InnoDB user=mysql #建议设置为系统最大内存的70% innodb_buffer_pool_size=6G #redo log日志大小,配置越大写操作的效率越高,但是崩溃恢复时间就会稍微长一点,SSD硬盘建议4G起配 innodb_log_file_size=4G #redo log缓存区域大小 innodb_log_buffer_size=8M #0是每秒刷新一次redo buffer到磁盘 #1是每次事务提交时都立即刷新到磁盘 #2是每次事务提交都立即刷新到os cache中,随后刷新到磁盘,机器断电才会丢失1秒数据 innodb_flush_log_at_trx_commit=2 #是否开启独立表空间 #开启后每个表数据都会单独存放在一个以表命名后缀为.ibd文件中 #5.6开始默认打开,通常也会打开,可以分散IO提升性能。 innodb_file_per_table=1 innodb_file_io_threads=4 #默认为fsync,代表刷数据到磁盘的时候会先申请系统级别的缓存 #改为O_DIRECT会跨过系统缓存直接写到磁盘 innodb_flush_method=O_DIRECT #脏页刷新速度,单位为页。磁盘速度越快就设置越大 innodb_io_capacity=2000 innodb_io_capacity_max=6000 innodb_lru_scan_depth=2000 innodb_thread_concurrency = 0 innodb_additional_mem_pool_size=16M innodb_autoinc_lock_mode = 2 ## Binary log ######################################################################### #事务落盘策略建议为1,代表每次有事务提交都刷新日志到磁盘中 sync_binlog=1 sync_relay_log=1 relay-log-info-repository=TABLE master-info-repository=TABLE #binlog过期天数 expire_logs_days=7 #binlog日志格式,推荐row binlog_format=ROW transaction-isolation=READ-COMMITTED ## cache ############################################################################ #内存临时表的最大值,默认64M tmp_table_size=512M character-set-server=utf8mb4 collation-server=utf8_general_ci skip-external-locking #如果客户端连接数上限后,允许多少个客户端进入一个队列排队 back_log=1024 #默认64M key_buffer_size=1024M #每个线程连接时申请的内存空间,默认0.2M thread_stack=256k #默认0.128M read_buffer_size=8M thread_cache_size=64 query_cache_size=128M max_heap_table_size=256M query_cache_type=1 binlog_cache_size = 2M #允许缓存客户端打开多少张表 table_open_cache=128 thread_cache=1024 thread_concurrency=8 #应用会话连接超时时间,单位为秒,默认为8小时 wait_timeout=60 join_buffer_size = 1024M sort_buffer_size = 8M read_rnd_buffer_size = 8M #connect #允许客户端连接失败的次数,超过该次数就会被服务拒绝连接 max_connect_errors=100000 max-connections=1000 # explicit_defaults_for_timestamp=true sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES # [mysqld_safe] #错误日志路径 log_error=/var/log/mysqld.log pid_file=/var/run/mysqld/mysqld.pid
写在最后:
完整的MySQL优化需要非常高深的功底,需要的不是一星半点的时间来学习和积累,对于我们这种菜鸡开发人员来说,强行吃下消化意义也不是很大,毕竟也只有面试能用用,也没其他地儿用了,应该被核心的精力放在写业务上,把业务涉及的SQL写的优雅就比较完美了,至于服务器优化这一块,术业有专攻,还是交给专业的人去做吧