性能分析的思路
- 首先需要使用【慢查询日志】功能,去获取所有查询时间比较长的SQL语句
- 其次【查看执行计划】查看有问题的SQL的执行计划 explain
- 最后可以使用【show profile[s]】 查看有问题的SQL的性能使用情况
1、慢查询日志
修改my.cnf开启:
slow_query_log=ON
long_query_time=3
slow_query_log_file=/var/lib/mysql/slow-log.log
慢查询日志介绍
开启慢查询功能
慢查询日志格式
tail -100f /var/lib/mysql/slow.log
分析慢查询日志的工具
使用mysqldumpslow工具
mysqldumpslow是MySQL自带的慢查询日志工具。
可以使用mysqldumpslow工具搜索慢查询日志中的SQL语句。
得到按照时间排序的前10条里面含有左连接的查询语句:
[root@localhost mysql] mysqldumpslow -s t -t 10 -g “left join”
/var/log/mysql/slow.log
常用参数说明:
参数 | 参数说明 |
---|---|
-s: | 是表示按照何种方式排序 |
c : | 访问计数 |
l : | 锁定时间 |
r : | 返回记录 |
al : | 平均锁定时间 |
ar : | 平均返回记录数 |
at : | 平均查询时间 |
-t : | 是top_n的意思,即返回前面n条数据 |
-g : | 后边可以写一个正则表达式,带小写不敏感 |
2、profile分析语句
Query Profiler是MySQL自带的一种query诊断分析工具,通过它可以分析出一条SQL语句的硬件性能瓶颈在什么地方。比如CPU,IO等,以及该SQL执行所耗费的时间等。不过该工具只有在MySQL 5.0.37以及以上版本中才有实现。默认的情况下,MYSQL的该功能没有打开,需要自己手动启动。
开启Profile功能
- Profile 功能由MySQL会话变量 : profiling控制,默认是OFF关闭状态。
- 查看是否开启了Profile功能:
select @@profiling;
show variables like ‘%profil%’
开启profile功能
set profiling=1; --1是开启、0是关闭
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 1 |
+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select count(id) from tuser;
ERROR 1046 (3D000): No database selected
mysql> use kkb_2;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select count(id) from tuser;
+-----------+
| count(id) |
+-----------+
| 10000000 |
+-----------+
1 row in set (4.62 sec)
mysql> show profiles;
+----------+------------+-----------------------------+
| Query_ID | Duration | Query |
+----------+------------+-----------------------------+
| 1 | 0.00016275 | select @@profiling |
| 2 | 0.00009200 | select count(id) from tuser |
| 3 | 0.00014875 | SELECT DATABASE() |
| 4 | 0.00066875 | show databases |
| 5 | 0.00021050 | show tables |
| 6 | 4.61513875 | select count(id) from tuser |
+----------+------------+-----------------------------+
6 rows in set, 1 warning (0.13 sec)
mysql> show profile for query 6;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000228 |
| checking permissions | 0.000018 |
| Opening tables | 0.000035 |
| init | 0.000204 |
| System lock | 0.000071 |
| optimizing | 0.000013 |
| statistics | 0.000067 |
| preparing | 0.000027 |
| executing | 0.000004 |
| Sending data | 4.614239 |
| end | 0.000045 |
| query end | 0.000009 |
| closing tables | 0.000026 |
| freeing items | 0.000019 |
| logging slow query | 0.000124 |
| cleaning up | 0.000011 |
+----------------------+----------+
16 rows in set, 1 warning (0.00 sec)
mysql> show profile cpu,block io,swaps for query 6;
+----------------------+----------+----------+------------+--------------+- --------------+-------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out | Swaps |
+----------------------+----------+----------+------------+--------------+- --------------+-------+
| starting | 0.000228 | 0.000361 | 0.000000 | 0 | 0 | 0 |
| checking permissions | 0.000018 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| Opening tables | 0.000035 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| init | 0.000204 | 0.000224 | 0.000000 | 0 | 0 | 0 |
| System lock | 0.000071 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| optimizing | 0.000013 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| statistics | 0.000067 | 0.000131 | 0.000000 | 0 | 0 | 0 |
| preparing | 0.000027 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| executing | 0.000004 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| Sending data | 4.614239 | 3.648639 | 0.543410 | 55280 | 0 | 0 |
| end | 0.000045 | 0.000202 | 0.000000 | 0 | 0 | 0 |
| query end | 0.000009 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| closing tables | 0.000026 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| freeing items | 0.000019 | 0.000000 | 0.000000 | 0 | 0 | 0 |
| logging slow query | 0.000124 | 0.000155 | 0.000000 | 0 | 8 | 0 |
| cleaning up | 0.000011 | 0.000000 | 0.000000 | 0 | 0 | 0 |
+----------------------+----------+----------+------------+--------------+- --------------+-------+
MySQL性能优化
1、服务器层面优化
服务器硬件优化
提升硬件设备,例如选择尽量高频率的内存(频率不能高于主板的支持)、提升网络带宽、使用SSD高速磁盘、提升CPU性能等。
CPU的选择:
- 对于数据库并发比较高的场景,CPU的数量比频率重要。
- 对于CPU密集型场景和频繁执行复杂SQL的场景,CPU的频率越高越好。
CentOS系统针对mysql的参数优化
MySQL数据库配置优化
配置文件
vim /etc/my.cnf
- 表示缓冲池字节大小。(InnoDB的内存越大,会减少磁盘交互)
推荐值为物理内存的50%~80%。默认128M。
innodb_buffer_pool_size
- 用来控制redo log刷新到磁盘的策略。(0性能最好,主从机制的机器,从机器是不负责写操作,所以不牵扯事务提交,所以为了从数据库的性能提升,需要将此参数设置为0)
innodb_flush_log_at_trx_commit=1
- 每提交1次事务同步写到磁盘中,可以设置为n。(主从机制的机器,从机器是不负责写操作,所以不牵扯事务提交,也不需要再重复写binlog,所以将此处设置为0)
sync_binlog=1
- 脏页占innodb_buffer_pool_size的比例时,触发刷脏页到磁盘。 推荐值为25%~50%。
sql innodb_max_dirty_pages_pct=30
- 后台进程最大IO性能指标。
默认200,如果SSD,调整为5000~20000
innodb_io_capacity=200
- 指定innodb共享表空间文件的大小。(分磁盘,这样可以减轻磁盘的IO操作性能压力)
innodb_data_file_path
- 慢查询日志的阈值设置,单位秒。(慢查询日志,不要随意开启,开启的时候,要合理设置阈值)
long_qurey_time=0.3
- mysql复制的形式,row为MySQL8.0的默认形式。(statement,row,mixed)
binlog_format=row
- 调高该参数则应降低interactive_timeout、wait_timeout的值。
max_connections=200
- 过大,实例恢复时间长;过小,造成日志切换频繁。(默认?)
innodb_log_file_size
- 全量日志建议关闭。
默认关闭。
general_log=0
将数据保存在内存中,保证从内存读取数据
- 设置足够大的 innodb_buffer_pool_size ,将数据读取到内存中。
建议innodb_buffer_pool_size设置为总内存大小的3/4或者4/5
- 怎样确定 innodb_buffer_pool_size 足够大。数据是从内存读取而不是硬盘?
降低磁盘写入次数
- 对于生产环境来说,很多日志是不需要开启的,比如:通用查询日志、慢查询日志、错误日志
- 使用足够大的写入缓存 innodb_log_file_size
推荐 innodb_log_file_size 设置为 0.25 * innodb_buffer_pool_size
- 设置合适的innodb_flush_log_at_trx_commit,和日志落盘有关系。
2、数据库设计层面优化
- 业务字段(预留字段)
- 系统字段(修改人、修改时间)
- 流程字段(状态)
具体优化方案如下:
- 设计中间表,一般针对于统计分析功能,或者实时性不高的需求(OLTP、OLAP)
- 为减少关联查询,创建合理的冗余字段(考虑数据库的三范式和查询性能的取舍,创建冗余字段还需要注意数据一致性问题)逆范式
- 对于字段太多的大表,考虑拆表(比如一个表有100多个字段)(查询的时候,是以整行为单位去
加载的。) - Blob Clob Text类型的字段,建议拆到另一张表中。(商品表10-15列,商品详情介绍[text]—建议将商品详情介绍拆成单独的表)
- 对于表中经常不被使用的字段或者存储数据比较多的字段,考虑拆表(比如商品表中会存储商品介绍,此时可以将商品介绍字段单独拆解到另一个表中,使用商品ID关联)
- 每张表建议都要有一个主键(主键索引),而且主键类型最好是int类型,建议自增主键(不考虑分布式系统的情况下)。
3、SQL语句优化
索引优化
- 为搜索字段(where中的条件)、排序字段、select查询列,创建合适的索引,不过要考虑数据的业务场景:查询多还是增删多?
- 尽量建立组合索引并注意组合索引的创建顺序,按照顺序组织查询条件、尽量将筛选粒度大的查询条件放到最左边。
- 尽量使用覆盖索引,SELECT语句中尽量不要使用 *。
- order by、group by语句要尽量使用到索引
name,age—组合索引
select * from user where name = “james” order by age ---- 使用到索引
select * from user order by name,age ---- 使用到索引
order by的索引生效,order by排序应该遵循最佳左前缀查询,如果是使用多个索引字段进行排序,那么排序的规则必须相同(同是升序或者降序),否则索引同样会失效。
- 索引长度尽量短,短索引可以节省索引空间,使查找的速度得到提升,同时内存中也可以装载更多的索引键值。太长的列,可以选择建立前缀索引
比如给name列加索引,但是name列长度为300。其实最多在使用索引搜索的时候,使用到前10个字节长度。这个时候建议建立前缀索引 - 索引更新不能频繁,更新非常频繁的数据不适宜建索引,因为维护索引的成本。
LIMIT优化
- 如果预计SELECT语句的查询结果是一条,最好使用 LIMIT 1,可以停止全表扫描。
SELECT * FROM user WHERE username=’全力詹’;
-- username没有建立唯一索引
SELECT * FROM user WHERE username=’全力詹’ LIMIT 1;
- 处理分页会使用到 LIMIT ,当翻页到非常靠后的页面的时候,偏移量会非常大,这时LIMIT的效率会非常差。 LIMIT OFFSET , SIZE;
LIMIT的优化问题,其实是 OFFSET 的问题,它会导致MySql扫描大量不需要的行然后再抛弃掉。
解决方案1:使用order by 和索引覆盖
- 原SQL(如果 film 表中的记录有10020条):
SELECT id,name FROM user LIMIT 10000, 20;
- 优化的SQL:
SELECT id,name FROM user ORDER BY id desc LIMIT 20;
解决方案2:使用子查询
SELECT id,name FROM (select id,name from user order by id limit 10000,20) t
解决方案3:
- 单表分页时,使用自增主键排序之后,先使用where条件 id > offset值,limit后面只写rows
其他查询优化
- 小表驱动大表,建议使用left join时,以小表关联大表,因为使用join的话,第一张表是必须全扫描的,以少关联多就可以减少这个扫描次数。
explain—select列的信息,显示查询顺序。
A表10万条记录
B表一千万条记录
需求:关联A表和B表去查询数据。比如结果能匹配的也就10条记录 - 避免全表扫描,mysql在使用不等于(!=或者<>)的时候无法使用导致全表扫描。在查询的时候,如果对索引使用不等于的操作将会导致索引失效,进行全表扫描
- 避免mysql放弃索引查询,如果mysql估计使用全表扫描要比使用索引快,则不使用索引。(最典型的场景就是数据量少的时候)尽量不使用count(*)、尽量使用count(主键)
COUNT(*)\COUNT(1)\COUNT(列),从索引使用情况来说,是一样的。如果COUNT(非索引列),那么MySQL会选择该表中,最小的那颗索引树,去进行统计。
COUNT(*)以行为统计,最终的结果是包含null值
COUNT(1),会过滤NULL值
COUNT(列),会过滤NULL
- JOIN两张表的关联字段最好都建立索引,而且最好字段类型是一样的
SELECT * FROM orders o LEFT JOIN user u on o.user_id = u.id
orders表中的user_id和user表中的id,类型要一致
- WHERE条件中尽量不要使用1=1、not in语句(建议使用not exists)
- 不用 MYSQL 内置的函数,因为内置函数不会建立查询缓存。
SQL查询语句和查询结果都会在第一次查询只会存储到MySQL的查询缓存中,
如果需要获取到查询缓存中的查询结果,
查询的SQL语句必须和第一次的查询SQL语句一致。
SELECT * FROM user where birthday = now();
- 合理利用慢查询日志、explain执行计划查询、show profile查看SQL执行时的资源使用情况。