JavaEE 企业级分布式高级架构师(六)MySQL学习笔记(6)
MySQL学习笔记
- 性能优化篇
- 性能优化的思路
- 慢查询日志
- 慢查询日志介绍
- 开启慢查询功能
- 演示一
- 演示二
- 分析慢查询日志
- MySQL自带的mysqldumpslow
- 使用percona-toolkit工具
- 分析pt-query-digest输出结果
- 用法示例
- 查看执行计划
- 建表语句
- EXPlAIN介绍
- 参数说明
- id
- select_type(重要)
- table
- type(重要)
- possible_keys
- key
- key_len
- ref
- rows
- Extra(重要)
- 参考网站
- SQL语句优化(开发人员)
- SQL设计层面优化
- 索引优化
- LIMIT优化
- 其他优化
- 案例
- MySQL实现随机获取几条数据的方法
- profile分析语句
- 介绍
- 语句使用
- 开启Profile功能
- 示例
- 服务器层面优化(了解)
- 缓冲区优化
- 降低磁盘写入次数
- MySQL数据库配置优化
- 操作系统优化
- 内核参数优化
- 增加资源限制
- 磁盘调度策略
- 服务器硬件优化
性能优化篇
性能优化的思路
- 首先需要使用慢查询日志功能,去获取所有查询时间比较长的SQL语句。
- 使用 explain 查看执行计划,查看有问题的SQL的执行计划。
- 针对查询慢的SQL语句进行优化。
- 使用 show profile[s] 查看有问题的SQL的性能使用情况。
- 调整操作系统参数优化。
- 升级服务器硬件。
慢查询日志
慢查询日志介绍
- 数据库查询快慢是影响项目性能的一大因素,对于数据库,我们除了要优化SQL,更需要的是先找到需要优化的SQL。
- MySQL数据库有一个“慢查询日志”功能,用来记录查询时间超过某个设定值的SQL,这将极大程度帮助我们快速定位到症结所在,以便对症下药。
- 至于查询时间的多少才算慢,每个项目、业务都有不同的要求。比如说,传统企业的软件允许查询时间高于某个值,但是把这个标准放在互联网项目或者访问量大的网站,估计就是一个bug,甚至可能升级为一个功能性缺陷。
- MySQL 的慢查询日志功能,默认是关闭的,需要手动开启。
开启慢查询功能
- 查看是否开启慢查询功能:
mysql> show variables like '%slow_query%';
+---------------------+-----------------------------------+
| Variable_name | Value |
+---------------------+-----------------------------------+
| slow_query_log | OFF |
| slow_query_log_file | /var/lib/mysql/centos128-slow.log |
+---------------------+-----------------------------------+
2 rows in set (0.00 sec)mysql> show variables like 'long_query_time%';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)
参数说明
- show_query_log:是否开启慢查询日志,ON为开启,OFF为关闭,如果为关闭可以开启。
- log_slow_queries:旧版(5.6以下版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件 host_name-slow.log。
- slow_query_log_file:新版(5.6及以上版本)MySQL数据库慢查询日志存储路径。可以不设置该参数,系统则会默认给一个缺省的文件 host_name-slow.log。
- long_query_time:慢查询阈值,当查询时间多于设定的阈值时,记录日志,单位为秒。
- 临时开启慢查询日志:在 MySQL 执行SQL语句设置,但是如果重启 MySQL 的会话将失效。
set global slow_query_log = ON;
set global long_query_time = 1;
- 永远开启慢查询功能:修改 /etc/my.cnf 配置文件,重启 MySQL,这种永久生效
[mysqld]
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/centos-host-slow.log
long_query_time = 1
- systemctl restart mysqld 重启mysql
演示一
- 慢查询日志格式
- 第一行,SQL查询执行的具体时间
- 第二行,执行SQL查询的连接信息,用户和连接IP
- 第三行,记录了一些我们比较有用的信息
- Query_time:这条 SQL 执行的时间,越长则越慢
- Lock_time:在MySQL服务器阶段(不是在存储引擎阶段)等待表锁时间
- Rows_sent:查询返回的行数
- Rows_examined:查询检查的行数,越长就当然越费时间
- 第四行,设置时间戳,没有实际意义,只是和第一行对应执行时间
- 第五行及后面所有行,执行的 SQL 语句记录信息,因为 SQL 可能会很长
演示二
- 构建一张大数据量的表
-- 创建表
CREATE TABLE `t_user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) NOT NULL,`age` int(11) NOT NULL DEFAULT '0',`address` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 插入一条记录
insert into t_user(name, age, address) values('张三', 23, '山上');
-- 递归插入,指数级增长
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
insert into t_user(name, age, address) select name, age, address from t_user;
- 执行查询:
mysql> select * from t_user where name like '%张%' limit 1;
+----+--------+-----+---------+
| id | name | age | address |
+----+--------+-----+---------+
| 1 | 张三 | 23 | 山上 |
+----+--------+-----+---------+
1 row in set (0.00 sec)mysql> select count(1) from t_user where name like '%张%';
+----------+
| count(1) |
+----------+
| 4194304 |
+----------+
1 row in set (1.31 sec)mysql> select * from t_user where name like '%xxx%';
Empty set (1.21 sec)
- 得到的慢查询日志:
分析慢查询日志
MySQL自带的mysqldumpslow
- 得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "sleep" /var/lib/mysql/centos-host-slow.log
- 常用参数说明:
- -s:是表示按照何种方式排序
- c:访问计数
- l:锁定时间
- r:返回记录
- t:查询时间
- al:平均锁定时间
- ar:平均返回记录数
- at:平均查询时间
- -t:是 top n 的意思,即返回前面多少条的记录
- -g:后边可以写一个正则匹配模式,大小写不敏感的
使用percona-toolkit工具
- percona-toolkit是一组高级命令工具的集合,可以查看当前服务器的摘要信息,磁盘检测,分析慢查询日志,查找重复索引,实现表同步等等。
# 下载&安装
wget http://www.percona.com/downloads/percona-toolkit/3.0.11/binary/tarball/percona-toolkit-3.0.11_x86_64.tar.gz
tar -zxvf percona-toolkit-3.0.11_x86_64.tar.gz -C /usr/apps/
cd /usr/apps/percona-toolkit-3.0.11
perl Makefile.PL
make
make install
- 可能出现的问题与解决方式:
# Can't locate ExtUtils/MakeMaker.pm in @INC 错误的解决方式:
yum install -y perl-ExtUtils-CBuilder per-ExtUtils-MakeMaker
# Can't locate Time/HiRes.pm in @INC 错误的解决方式:
yum install -y perl-Time-HiRes
# Can't locate Digest/MD5.pm in @INC 错误的解决方式:
yum install -y perl-Digest-MD5
- 使用 pt-query-disget 查询慢查询日志
pt-query-digest /var/lib/mysql/localhost-slow.log
分析pt-query-digest输出结果
- 第一部分:总体统计结果。
- Overall:总共有多少条查询;
- Time range:查询执行的时间范围;
- unique:唯一查询数量,即对查询条件进行参数化以后,总共有多少不同的查询;
- total:总计;
- min:最小;
- max:最大;
- avg:平均;
- 95%:把所有值从小到大排列,位置位于95%的那个数,这个数一般最具有参考价值;
- median:中位数,把所有值从小到大排列,位置位于中间那个数。
[root@centos-host ~]# pt-query-digest /var/lib/mysql/centos-host-slow.log
---
== 该工具执行日志分析的用户时间,系统时间,物理内存占用大小,虚拟内存占用大小
# 310ms user time, 510ms system time, 22.23M rss, 186.80M vsz
== 工具执行时间
# Current date: Sat Mar 23 20:23:45 2019
== 运行分析工具的主机名
# Hostname: centos-host
== 被分析的文件名
# Files: /var/lib/mysql/centos-host-slow.log
== 语句总数量,唯一的语句数量,QPS,并发数
# Overall: 2 total, 1 unique, 0.00 QPS, 0.02x concurrency ________________
== 日志记录的时间范围
# Time range: 2019-03-21 21:39:10 to 21:47:38
== 属性 总计 最小 最大 平均 标准 中等
# Attribute total min max avg 95% stddev median
# ============ ======= ======= ======= ======= ======= ======= =======
== 语句执行时间
# Exec time 8s 3s 5s 4s 5s 1s 4s
== 锁占用时间
# Lock time 0 0 0 0 0 0 0
== 发送到客户端和行数
# Rows sent 2 1 1 1 1 0 1
== select 语句扫描行数
# Rows examine 0 0 0 0 0 0 0
== 查询的字符数
# Query size 30 15 15 15 15 0 15
- 第二部分:查询分析统计结果。
- Rank:所有语句的排名,默认按查询时间降序排列,通过 order by 指定;
- Query ID:语句的ID,(去掉多余空格和文本字符,计算hash);
- Response:总的响应时间;
- time:该查询在本次分析中总的时间占比;
- Calls:执行次数,即本次分析总共有多少条这种类型的查询语句;
- R/Call:平均每次执行的响应时间;
- V/M:响应时间Variance-to-mean的比率;
- Item:查询对象。
# Profile
# Rank Query ID Response time Calls R/Call V/M
# ==== ================================== ============= ===== ====== =====
# 1 0x59A74D08D407B5EDF9A57DD5A41825CA 8.0010 100.0% 2 4.0005 0.50 SELECT
- 第三部分:每一种查询的详细统计结果,由下面的详细统计结果,最上面的表格列出了执行次数、最大、最小、平均、95%等各项目的统计。
- ID:查询的ID号,和上图的Query IDd对应;
- Databases:数据库名;
- Users:各个用户执行的次数(占比);
- Query_time distribution:查询时间分布,长短体现区间占比,本例中1s-10s之间查询数量是10s以上的两倍;
- Tables:查询中涉及到的表;
- Explain:SQL语句。
# Query 1: 0.00 QPS, 0.02x concurrency, ID 0x59A74D08D407B5EDF9A57DD5A41825CA at byte 923
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.50
# Time range: 2019-03-21 21:39:10 to 21:47:38
# Attribute pct total min max avg 95% stddev median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count 100 2
# Exec time 100 8s 3s 5s 4s 5s 1s 4s
# Lock time 0 0 0 0 0 0 0 0
# Rows sent 100 2 1 1 1 1 0 1
# Rows examine 0 0 0 0 0 0 0 0
# Query size 100 30 15 15 15 15 0 15
# String:
# Hosts localhost
# Users root
# Query_time distribution
# 1us
# 10us
# 100us
# 1ms
# 10ms
# 100ms
# 1s ################################################################
# 10s+
# EXPLAIN /*!50100 PARTITIONS*/
select sleep(5)\G
用法示例
- 直接分析慢查询文件:
pt-query-digest slow.log > slow_report.log
- 分析最近12小时内的查询:
pt-query-digest --since=12h slow.log > slow_report2.log
- 分析指定时间范围内的查询:
pt-query-digest slow.log --since '2017-01-07 09:30:00' --until '2017-01-07 10:00:00' > slow_report3.log
- 分析只含有 select 语句的慢查询:
pt-query-digest --filter '$event->{fingerprint} =~ m/^select/i' slow.log > slow_report4.log
- 针对们某个用户的慢查询:
pt-query-digest --filter '($event->{user} || "") =~ m/^root/i' slow.log > slow_report5.log
- 查询所有的全表扫描或 full join 的慢查询:
pt-query-digest --filter '(($event->{Full_scan} || "") eq "yes") || (($event->{Full_join} || "") eq "yes")' slow.log > slow_report6.log
- 把查询保存到 query_review 表:
pt-query-digest --user=root --password=abc123 --review h=localhost, D=test, t=query_review --create-review-table slow.log
- 把查询保存到 query_history 表:
pt-query-digest --user=root --password=abc123 --review h=localhost, D=test, t=query_history --create-review-table slow.log_0001
pt-query-digest --user=root --password=abc123 --review h=localhost, D=test, t=query_history --create-review-table slow.log_0002
- 通过 tcpdump 抓取 mysql 的 tcp 协议数据,然后再分析:
tcpdump -s 65535 -x -nn -q -tttt -i any -c 1000 port 3306 > mysql.tcp.txt
pt-query-digest --type tcpdump mysql.tcp.txt > slow_report9.log
- 分析binlog:
mysqlbinlog mysql-bin.000093 > mysql-bin000093.sql
pt-query-digest --type=binlog mysql-bin000093.sql > slow_report10.log
- 分析general log:
pt-query-digest --type=genlog localhost.log > slow_report11.log
查看执行计划
建表语句
create table user(id int primary key,name varchar(100),age int,sex char(1),address varchar(100)
) charset=utf8;
-- 创建索引
alter table user add index idx_name_age(name(100), age); -- 组合索引
alter table user add index idx_sex(sex(1)); -- 非唯一索引
-- 初始化数据
insert into user values(1, 'zhangsan', 20, '0', '致真大厦');
EXPlAIN介绍
- MySQL 提供了一个 EXPlAIN 命令,可以对 SELECT 语句的执行计划进行分析,并输出 SELECT 执行的详细信息,以供开发人员针对性优化。
- 使用 explain 命令来查看SQL执行计划,如:该SQL语句有没有使用上索引,有没有做全表扫描等
- 可以通过 explain 命令深入了解 MySQL的基于开销的优化器,还可以获得很多可能被优化器考虑到的访问策略的细节,以及当运行 SQL 语句时哪种策略预计会被优化器采用。
- explain 命令用法十分简单,在SELECT 语句前面加上 explain 就可以了,例如:
参数说明
explain 出来的信息有 10 列,分别是
id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra
参数 | 说明 |
---|---|
id | SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符 |
select_type | SELECT 查询的类型. |
table | 查询的是哪个表 |
partitions | 匹配的分区 |
type | join 类型 |
possible_keys | 此次查询中可能选用的索引 |
key | 此次查询中确切使用到的索引 |
key_len | 此次查询中使用到的索引长度 |
ref | 哪个字段或常数与 key 一起被使用 |
rows | 显示此查询一共扫描了多少行,这个是一个估计值 |
filtered | 表示此查询条件所过滤的数据的百分比 |
extra | 额外的信息 |
id
每个 SELECT 语句都会自动分配一个唯一标识符,表示查询中操作的顺序,有四种情况:
- id相同:执行顺序由上到下
- id不同:如果是子查询,id号会自增,id越大,优先级越高
- id相同的不同的 同时存在
- id列为 null 的就表示这是一个结果集,不需要使用它来进行查询
select_type(重要)
单位查询查询类型,主要用于区别普通查询、联合查询(union、union all)、子查询等复杂查询
- simple:表示不需要 union 操作或者不包含子查询的简单 select 查询。有连接查询时,外层的查询为 simple,且只有一个。
- primary:一个需要 union 操作或者含有子查询的 select,位于最外层的查询,且只有一个。
- subquery:除了 from 子句中包含的子查询外,其他地方出现的子查询都可能是subquery。
- union:union连接的两个 select 查询,第一个查询是 dervied 派生表,除了第一个表外,第二个以后的表类型都是union。
- union result:包含 union 的结果集,在 union 和 union all 语句中,因为它不需要参与查询,所以 id 字段为null。
- dependent union:与union一样,出现在union 或 union all 语句中,但是这个查询要受到外部查询的影响。
- dependent subquery:与 dependent union 类似,表示这个 subquery 的查询要受到外部表查询的影响。
- derived:from 子句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套select。
table
显示单位查询的表名,有如下几种情况:
- 如果查询使用了别名,那么这里显示的是别名
- 如果不涉及对数据表的操作,那么这里显示的为 null
- 如果显示为尖括号括起来的就表示这个是临时表,后面的 N 就是执行计划中的id,表示结果来自于这个查询产生
- 如果是尖括号括起来的<union M, N>,也是一个临时表,表示这个结果来自于 union 查询 id 为 M,N的结果集
type(重要)
显示的是单位查询的连接类型或者理解为访问类型,访问性能依次从好到差:
system
const
eq_ref
ref
fulltext
ref_or_null
unique_subquery
index_subquery
range
index_merge
index
ALL
注意事项:
* 除了 ALL 之外,其他的 type 都可以使用到索引
* 除了 index_merge 之外,其他的 type 只可以用到一个索引
* 最少要索引使用到 range 级别
- system:表中只有一行数据或者是空表
- const(重要):使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const。其他数据库也叫做唯一索引扫描
- eq_ref(重要):使用唯一索引或者主键,此类型通常出现在多表的 join 查询,表示对于前表的每一个结果,都只匹配到后表的一行结果,并且查询的比较操作通常是
=
,查询效率较高
- ref(重要):针对非唯一索引,使用等值(=)查询,或者是使用了最左前缀规则索引的查询。
- fulltext:全文索引检索,优先级最高,若全文索引和普通索引同时存在,MySQL不管代价,优先选择使用全文索引
- ref_or_null:与ref方法类似,只是增加了 null 值比较,实际用的不多
- unique_subquery:用于 where 中的 int 形式子查询,子查询返回不重复值
- index_subquery:用于 in 形式子查询使用到了辅助索引或者 in 常数列表,子查询可能返回重复值,可以使用索引将子查询去重
- range(重要):索引范围扫描,常见于使用 >、<、is null、between、in、like 等运算符的查询中
- index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见 and、or 的条件使用了不同的索引,官方排序这个在 ref_or_null 之后,但是实际上由于要读取多个索引,性能可能大部分时间都不如 range
- index(重要):条件是出现在索引树中的节点的,可能没有完全匹配索引
全索引扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询,可以使用索引排序或者分组的查询
- ALL(重要):这个就是全表扫描数据文件,然后再在 Server 层进行过滤返回符合要求的记录
possible_keys
此次查询中可能选用的索引,一个或多个
key
查询真正使用到的索引,select_type 为 index_merge 时,这里可能出现两个以上的索引,其他的 select_type 这里只会出现一个
key_len
- 用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。
- 留意这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了
- 另外,key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中
ref
- 如果是使用的常数等值查询,这里会显示 const
- 如果是连接查询,被驱动表的执行计划这里回显示驱动表的关联字段
- 如果是条件使用了表达式或者函数,或者条件发生了内部隐式转换,这里可能显示为 func
rows
这里是执行计划中估算的扫描行数,不是精确值(InnoDB不是精确的值,MyISAM是精确值,主要原因是InnoDB里面使用了MVCC并发机制)
Extra(重要)
这个列包含不适合在其他列中显示但十分重要的额外信息,这个列可以显示的信息非常多,有几十种,常用的有:
- using filesort(重要):
* 排序时无法使用到索引时,就会出现这个。常见于 order by 和 group by 语句中。
* 说明MySQL会使用一个外部的索引排序,而不是按照索引顺序进行读取
* MySQL中无法利用所有完成的排序操作称为“文件排序”
- using index(重要):查询时不需要回表查询,直接通过索引就可以获取查询的数据
* 表示相应的SELECT查询中,使用到了【覆盖索引(Covering Index)】,避免访问表的数据行,效率不错
* 如果同时出现 Using Where,说明索引被用来执行查找索引键值
* 如果没有同时出现 Using Where,说明索引用来读取数据而非执行查找动作
- using where(重要):表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。
- using index condition(重要):using index condition会先条件过滤,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行;因为MySQL的架构原因,分成了 server 层和引擎层,才有所谓的“下推”说法。所以ICP(Index Condition Pushdown,索引下推)其实就是实现了 index filter 技术,将原来的在 server 层进行的 table filter 中可以进行 index filter的部分,在引擎层使用 index filter 进行处理,不再需要回表进行 table filter。
查询条件中分为限制条件和检查条件,5.6之前,存储引擎只能根据限制条件扫描数据并返回,然后server层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x之后支持ICP特性,可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。
- using temporary:
* 表示使用了临时表存储中间结果
* MySQL在对查询结果 order by 和 group by 时使用临时表
* 临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看 status 变量,used_tmp_table,used_tmp_disk_table才能看出来。
- using join buffer(block nested loop),using join buffer(batched key accss):5.6.x之后的版本优化关联查询的BNL,BKA特性。主要是减少内表的循环数量以及比较顺序地扫描查询。
- using sort_union,using_union,using intersect,using sort_intersection:
* using intersect:表示使用 and 的各个索引的条件时,该信息表示是从处理结果获取交集
* using union:表示使用 or 连接各个使用索引的条件时,该信息表示从处理结果获取并集
* using sort_union 和 using sort_intersection:与前面两个对应的类似,只是他们是出现在用 and 和 or查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。
- distinct:在 select 部分使用了 distinct 关键字
- no tables used:不带from字句的查询或者 from dual查询。使用not in()形式子查询或not exists运算符的连接查询,叫做反连接,即:一般连接查询是先查询内表,再查询外表;而反连接查询是先查询外表,再查询内表。
- firstmatch(tb_name):5.6.x开始引入的优化子查询的新特性之一,常见于 where 字句含有 in() 类型的子查询。如果内表的数据量比较大,就可能出现这个。
- loosescan(m…n):5.6.x之后引入的优化子查询的新特性之一,在 in() 类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个。除了这些之外,还有很多查询数据字典库,执行计划过程中就发现不可能存在结果的一些提示信息。
- filtered:使用explain extended时会出现这个列,5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。
参考网站
https://segmentfault.com/a/1190000008131735
https://blog.csdn.net/rewiner120/article/details/70598797
SQL语句优化(开发人员)
SQL设计层面优化
面对人群:懂技术并且了解需求的程序员
具体优化方案如下:
- 设计中间表,一般针对统计分析功能,或者实时性不要的需求(OLTP、OLAP)
- 为减少关联查询,创建合理的冗余字段(考虑数据库的三范式和查询性能的取舍,创建冗余字段还需要注意数据一致性问题)
- 对于字段太多的大表,考虑拆表(比如一个表有100多个字段)
- 对于表中经常不被使用的字段或者存储数据比较多的字段,考虑拆表(比如商品表中会存储商品介绍,此时可以将商品介绍字段单独拆解到另一个表中,使用商品ID关联)
- 每张表建议都要有一个主键(主键索引),而且主键类型最好是 int 类型,建议自增主键(不考虑分布式系统的情况下)
索引优化
- 为搜索字段(where中的条件)、排序字段、select查询列,创建合适的索引,不过要考虑数据的业务场景:查询多还是增删多?
- 尽量建立组合索引,并注意组合索引创建的顺序,按照顺序组织查询条件、尽量将筛选粒度大的查询条件放到最左边。
- 尽量使用覆盖索引,select 语句中尽量不要使用 *。
- order by、group by语句要尽量使用到索引。
- 索引长度尽量短,短索引可以节省索引空间,使查找的速度得到提升,同时内存中也可以装载更多的索引键值。太长的列,可以选择建立前缀索引。
- 索引更新不能频繁,更新非常频繁的数据不适宜建索引,因为维护索引的成本。
- order by的索引生效,order by排序应该遵循最佳左前缀查询,如果是使用多个索引字段进行排序,那么排序的规则必须相同(同是升序或者降序),否则索引同样会失效。
LIMIT优化
- 如果预计 select 语句的查询结果是一条,最好使用 limit 1,可以停止全表扫描,例如:
select * from user where username = 'zhangsan'; -- username没有建立唯一索引
select * from user where username = 'zhangsan' limit 1;
- 处理分页会使用到 limit,当翻页到非常靠后的页面的时候,偏移量会非常大,这时 limit 的效率会非常差。LIMIT OFFSET, SIZE;,LIMIT 的优化问题,其实是 OFFSET 的问题,它会导致 MySQL 扫描大量不需要的行然后再抛弃掉。
解决方案1:使用order by 和 索引覆盖
-- 原SQL(如果 film 表中的记录有100020条)
select film_id, description from film limit 100, 20;
select film_id, description from film limit 100000, 20;
-- 优化的SQL
select film_id, description from film order by 主键 limit 20;
解决方案2:使用子查询
-- 原SQL
select * from film limit 100000, 20;
-- 优化的SQL
select * from film where id >= (select id from order by id limit 100000, 1) limit 20;
解决方案3:单表分页时,使用自增主键排序之后,先使用 where 条件 id > offset 值,limit 后面只写 rows
select * from film where id > offset limit 20
其他优化
- 小表驱动大表,建议使用 left join 时,以小表关联大表,因为使用 join 的话,第一张表必须是全表扫描,以少关联多就可以减少这个扫描次数。
- 避免全表扫描,mysql 在使用不等于(!=或<>)时,无法使用索引导致全表扫描。在查询时,如果对索引使用不等于操作会导致索引失效,进行全表扫描。
- 尽量不使用 count(*)、尽量使用 count(主键)
* count(*):查询行数,是会遍历所有的行、所有的列
* count(列):查询指定列不为 null 的行数(过滤null),如果列可以为空,则 count(*) 不等于 count(列),除非指定的列是非空才会是 count(*) 等于 count(列)
* count(伪列):比如count(1)
- 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执行时的资源使用情况。
案例
MySQL实现随机获取几条数据的方法
sql语句有几种写法:
【1】SELECT * FROM tablename ORDER BY RAND() LIMIT 想要获取的数据条数;
【2】SELECT * FROM `table` WHERE id >= (SELECT FLOOR( MAX(id) * RAND()) FROM `table` ) ORDER BY id LIMIT 想要获取的数据条数;
【3】SELECT * FROM `table` AS t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `table`)) AS id) AS t2 WHERE t1.id >= t2.id ORDER BY t1.id ASC LIMIT 想要获取的数据条数;
【4】SELECT * FROM `table`WHERE id >= (SELECT floor(RAND() * (SELECT MAX(id) FROM `table`))) ORDER BY id LIMIT 想要获取的数据条数;
【5】SELECT * FROM `table` WHERE id >= (SELECT floor( RAND() * ((SELECT MAX(id) FROM `table`)-(SELECT MIN(id) FROM `table`)) + (SELECT MIN(id) FROM `table`))) ORDER BY id LIMIT 想要获取的数据条数;
【6】SELECT * FROM `table` AS t1 JOIN (SELECT ROUND(RAND() * ((SELECT MAX(id) FROM `table`)-(SELECT MIN(id) FROM `table`))+(SELECT MIN(id) FROM `table`)) AS id) AS t2 WHERE t1.id >= t2.id ORDER BY t1.id LIMIT 想要获取的数据条数;
1的查询时间>>2的查询时间>>5的查询时间>6的查询时间>4的查询时间>3的查询时间,也就是3的效率最高。
profile分析语句
介绍
- Query profiler是MySQL自带的一种query诊断分析工具,通过它可以分析出一条SQL语句的硬件性能瓶颈在什么地方。
- **通常我们是使用的explain,以及slow query log都无法做到精确分析,但是Query Profiler却可以定位出一条SQL语句执行的各种资源消耗情况,比如CPU,IO等,以及该SQL执行所耗费的时间等。**不过该工具只有在MYSQL 5.0.37以及以上版本中才有实现。
- 默认的情况下,MYSQL的该功能没有打开,需要自己手动启动。
语句使用
- show profile 和 show profiles 语句可以展示当前会话(退出 session后,profile重置为0)中执行语句的资源使用情况。
- show profiles:以列表形式显示最近发送到服务器上执行的语句的资源使用情况,显示的记录由变量【profiling_history_size】控制,默认15条
- show profile:展示最近一条语句执行的详细资源占用信息,默认显示 Status 和 Duration 两列
- show profile 还可以根据 show profiles 列表中的 Query_ID,选择显示某条记录的性能分析信息
语法结构:SHOW PROFILE [type [, type] ... ] [FOR QUERY n] [LIMIT row_count [OFFSET offset]]
type:ALLBLOCK IOCONTEXT SWITCHESCPUIPCMEMORYPAGE FAULTSSOURCESWAPS
开启Profile功能
- profile 功能由MySQL会话变量【profiling】控制,默认是 OFF 关闭状态
- 查看是否开启 Profile 功能:
select @@profiling;
show variables like '%profil%';
- 开启 profile 功能:
# 1:开启,0:关闭
set profiling = 1;
示例
- 执行【set profiling = 1】打开 profiling 功能:
- 执行SQL语句:
- 执行 show profiles 查看分析吧列表
- 查看第二条语句的执行情况:
- 可指定资源类型查询:
服务器层面优化(了解)
缓冲区优化
- 将数据保存在内存中,保证从内存读取数据。
- 设置足够大的 innodb_buffer_pool_size,将数据读取到内存中。建议 innodb_buffer_pool_size 设置为总内存大小的 3/4 或者 4/5。
怎样确定 innodb_buffer_pool_size 足够大,数据是从内存读取而不是硬盘?
mysql> show global status like 'innodb_buffer_pool_pages_%';
+----------------------------------+-------+
| Variable_name | Value |
+----------------------------------+-------+
| Innodb_buffer_pool_pages_data | 8189 |
| Innodb_buffer_pool_pages_dirty | 0 |
| Innodb_buffer_pool_pages_flushed | 15084 |
| Innodb_buffer_pool_pages_free | 1 |
| Innodb_buffer_pool_pages_misc | 1 |
| Innodb_buffer_pool_pages_total | 8191 |
+----------------------------------+-------+
6 rows in set (0.00 sec)
降低磁盘写入次数
- 对于生产环境来说,很多日志是不需要开启的,比如:通用查询日志、慢查询日志、错误日志。
- 使用足够大的写入缓存 innodb_buffer_pool_size,推荐合适的 innodb_buffer_pool_size 设置为 0.25*innodb_buffer_pool_size。
- 设置合适的 innodb_flush_log_at_trx_commit,和日志落盘有关系。
MySQL数据库配置优化
- innodb_buffer_pool_size:表示缓冲池字节大小。推荐值为物理内存的50%~80%。
- innodb_flush_log_at_trx_commit=1:用来控制redo log刷新到磁盘的策略。
- sync_binlog=1:每提交1次事务同步写到磁盘中,可以设置为n。
- innodb_max_dirty_pages_pct=30:脏页占innodb_buffer_pool_size的比例时,触发刷脏页到磁盘。 推荐值为25%~50%。
- innodb_io_capacity=200:后台进程最大IO性能指标。默认200,如果SSD,调整为5000~20000。
在MySQL5.1.X版本中,由于代码写死,因此最多只会刷新100个脏页到磁盘、合并20个插入缓冲,即使磁盘有能力处理更多的请求,也只会处理这么多,这样在更新量较大(比如大批量INSERT)的时候,脏页刷新可能就会跟不上,导致性能下降。
而在MySQL5.5.X版本里,innodb_io_capacity参数可以动态调整刷新脏页的数量,这在一定程度上解决了这一问题。
innodb_io_capacity参数默认是200,单位是页。该参数设置的大小取决于硬盘的IOPS,即每秒的输入输出量(或读写次数)。
至于什么样的磁盘配置应该设置innodb_io_capacity参数的值是多少,大家可参考下表。
- innodb_data_file_path:指定innodb共享表空间文件的大小。
- long_qurey_time=0.3:慢查询日志的阈值设置,单位秒。
- binlog_format=row:mysql复制的形式,row为MySQL8.0的默认形式。
- max_connections=200:调高该参数则应降低interactive_timeout、wait_timeout的值。
- innodb_log_file_size:过大,实例恢复时间长;过小,造成日志切换频繁。
- general_log=0:全量日志建议关闭。 默认关闭。
操作系统优化
内核参数优化
- CentOS系统针对mysql的参数优化:内核相关参数(/etc/sysctl.conf),以下参数可以直接放到sysctl.conf文件的末尾。
- 增加监听队列上限:
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535
- 加快TCP连接的回收:
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
- TCP连接接收和发送缓冲区大小的默认值和最大值:
net.core.wmem_default = 87380
net.core.wmem_max = 16777216
net.core.rmem_default = 87380
net.core.rmem_max = 16777216
- 减少失效连接所占用的TCP资源的数量,加快资源回收的效率:
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
- 单个共享内存段的最大值:这个参数应该设置的足够大,以便能在一个共享内存段下容纳整个的Innodb缓冲池的大小。这个值的大小对于64位linux系统,可取的最大值为(物理内存值-1)byte,建议值为大于物理内存的一半,一般取值大于Innodb缓冲池的大小即可。
kernel.shmmax = 4294967295
- 控制换出运行时内存的相对权重:这个参数当内存不足时会对性能产生比较明显的影响。(设置为0,表示Linux内核虚拟内存完全被占用,才会要使用交换区。)
vm.swappiness = 0
Linux系统内存交换区?
在Linux系统安装时都会有一个特殊的磁盘分区,称之为系统交换分区。
使用 free -m 命令可以看到swap就是内存交换区。
作用:当操作系统没有足够的内存时,就会将部分虚拟内存写到磁盘的交换区中,这样就会发生内存交换。
- 如果Linux系统上完全禁用交换分区,带来的风险:降低操作系统的性能;容易造成内存溢出,崩溃,或都被操作系统kill掉。
增加资源限制
- 打开文件数的限制以下参数可以直接放到(/etc/security/limit.conf)文件的末尾:
* soft nofile 65535
* hard nofile 65535
*:表示对所有用户有效
soft:表示当前系统生效的设置(soft不能大于hard )
hard:表明系统中所能设定的最大值
nofile:表示所限制的资源是打开文件的最大数目 65535:限制的数量
- 以上两行配置将可打开的文件数量增加到65535个,以保证可以打开足够多的文件句柄。
注意:这个文件的修改需要重启系统才能生效。
磁盘调度策略
- cfq(完全公平队列策略,Linux2.6.18之后内核的系统默认策略):该模式按进程创建多个队列,各个进程发来的IO请求会被cfq以轮循方式处理,对每个IO请求都是公平的。该策略适合离散读的应用。
- deadline (截止时间调度策略):deadline,包含读和写两个队列,确保在一个截止时间内服务请求(截止时间是可调整的),而默认读期限短于写期限。这样就防止了写操作因为不能被读取而饿死的现象,deadline对数据库类应用是最好的选择。
- noop (电梯式调度策略):noop只实现一个简单的FIFO队列,倾向饿死读而利于写,因此noop对于闪存设备、RAM及嵌入式系统是最好的选择。
- anticipatory (预料I/O调度策略):本质上与deadline策略一样,但在最后一次读操作之后,要等待6ms,才能继续进行对其它I/O请求进行调度。它会在每个6ms中插入新的I/O操作,合并写入流,用写入延时换取最大的写入吞吐量。
- anticipatory适合于写入较多的环境,比如文件服务器。该策略对数据库环境表现很差。
- 查看调度策略的方法:
cat /sys/block/devname/queue/scheduler
- 修改调度策略的方法:
echo > /sys/block/devname/queue/scheduler
服务器硬件优化
- 提升硬件设备,例如选择尽量高频率的内存(频率不能高于主板的支持)、提升网络带宽、使用SSD高速磁盘、提升CPU性能等。
- CPU的选择:
- 对于数据库并发比较高的场景,CPU的数量比频率重要。
- 对于CPU密集型场景和频繁执行复杂SQL的场景,CPU的频率越高越好。
JavaEE 企业级分布式高级架构师(六)MySQL学习笔记(6)相关推荐
- JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(1)
Spring Cloud学习笔记 Spring Cloud入门 分布式技术图谱 Spring Cloud简介 官网介绍 百度百科 总结 Spring Cloud的国内使用情况 Spring Cloud ...
- JavaEE 企业级分布式高级架构师(十七)ElasticSearch全文检索(1)
ElasticSearch学习笔记 基础篇 问题思考 大规模数据如何检索? 传统数据库的应对解决方案? 非关系型数据库的解决方案? 另辟蹊径--完全把数据放入内存怎么样? 全文检索技术 什么是全文检索 ...
- JavaEE 企业级分布式高级架构师(二十)RocketMQ学习笔记(2)
RocketMQ学习笔记 进阶篇 消息样例 普通消息 消息发送 发送同步消息 发送异步消息 单向发送消息 三种发送方式的对比 消费消息 顺序消息 如何保证顺序 顺序的实现 MessageListene ...
- JavaEE 企业级分布式高级架构师(十八)容器虚拟化技术(3)
Kubernetes学习笔记 K8S集群服务搭建 环境准备 机器环境 依赖环境 docker部署 kubeadm(一键安装k8s) 集群安装 依赖镜像 k8s部署 flannel插件 节点Join 节 ...
- JavaEE 企业级分布式高级架构师(十五)FastDFS分布式文件服务器(1)
FastDFS学习笔记 FastDFS介绍 传统文件存储弊端 FastDFS是什么 为什么使用FastDFS FastDFS架构原理分析 架构整体分析 Tracker Server跟踪服务器 Stor ...
- JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(4)
SpringMVC学习笔记 高级应用篇 ControllerAdvice @ControllerAdvice @ModelAttribute 作用于方法 作用于方法参数 @InitBinder @Ex ...
- JavaEE 企业级分布式高级架构师课程_汇总贴
总目录: 第一课(2018.7.10) 01 mybatis框架整体概况(2018.7.10)- 转载于:https://www.cnblogs.com/wangjunwei/p/10424103.h ...
- JavaEE 企业级分布式高级架构师(十五)FastDFS分布式文件服务器(3)
FastDFS学习笔记 Java操作FastDFS 测试文件上传 Spring Boot整合FastDFS 实现图片压缩 FastDFS主从文件 应用背景 解决办法 流程说明 Java代码 Nginx ...
- JavaEE 企业级分布式高级架构师(十七)ElasticSearch全文检索(5)
ElasticSearch学习笔记 性能优化篇 ElasticSearch部署 选择合理的硬件配置--尽可能使用 SSD 给JVM配置机器一半的内存,但是不建议超过32G 规模较大的集群配置专有主节点 ...
最新文章
- Spring Cloud架构的各个组件的原理分析
- C++中四种类型转换符:static_cast、dynamic_cast、reinterpret_cast和const_cast要点解析
- 初始化JQuery方法与(function(){})(para)匿名方法介绍
- my.ini修改后服务无法启动_Spring Cloud Eureka 服务实现不停机(Zero-downtime)部署
- 想要轻松制作GIF图片,来看篇超全面的分析!
- DXperience 换肤
- 计算机控制的行业规模,2019年中国DCS控制系统行业市场现状及竞争格局分析,内资“两家独大”「图」...
- js—封装原生AJAX
- web测试之功能测试总结
- 【高等数学】微积分----教你如何简单地推导求导公式(二)
- 利用spring集成redis使用
- 写贺卡给毕业师姐怎么写计算机系的,[给师姐的毕业祝福语]对师姐的毕业祝福语...
- java web服务_如何用Java实现Web服务器
- 接口测试 — 使用Requests库发送POST请求
- 智慧交通系统平台建设方案(附下载)
- latex/texlive行超出正文(间)公式或文字超出。
- NUMA与英特尔下一代Xeon处理器学习心得
- 毛玻璃matlab,QA清单(毛玻璃赛题)
- 已经一点经纬度和距离,计算另一点的经纬度
- 用python做自动化控制-用 Python 自动化办公能做到哪些有趣或有用的事情?