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 的查询要受到外部表查询的影响

  • derivedfrom 子句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套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 profileshow 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)相关推荐

  1. JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(1)

    Spring Cloud学习笔记 Spring Cloud入门 分布式技术图谱 Spring Cloud简介 官网介绍 百度百科 总结 Spring Cloud的国内使用情况 Spring Cloud ...

  2. JavaEE 企业级分布式高级架构师(十七)ElasticSearch全文检索(1)

    ElasticSearch学习笔记 基础篇 问题思考 大规模数据如何检索? 传统数据库的应对解决方案? 非关系型数据库的解决方案? 另辟蹊径--完全把数据放入内存怎么样? 全文检索技术 什么是全文检索 ...

  3. JavaEE 企业级分布式高级架构师(二十)RocketMQ学习笔记(2)

    RocketMQ学习笔记 进阶篇 消息样例 普通消息 消息发送 发送同步消息 发送异步消息 单向发送消息 三种发送方式的对比 消费消息 顺序消息 如何保证顺序 顺序的实现 MessageListene ...

  4. JavaEE 企业级分布式高级架构师(十八)容器虚拟化技术(3)

    Kubernetes学习笔记 K8S集群服务搭建 环境准备 机器环境 依赖环境 docker部署 kubeadm(一键安装k8s) 集群安装 依赖镜像 k8s部署 flannel插件 节点Join 节 ...

  5. JavaEE 企业级分布式高级架构师(十五)FastDFS分布式文件服务器(1)

    FastDFS学习笔记 FastDFS介绍 传统文件存储弊端 FastDFS是什么 为什么使用FastDFS FastDFS架构原理分析 架构整体分析 Tracker Server跟踪服务器 Stor ...

  6. JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(4)

    SpringMVC学习笔记 高级应用篇 ControllerAdvice @ControllerAdvice @ModelAttribute 作用于方法 作用于方法参数 @InitBinder @Ex ...

  7. JavaEE 企业级分布式高级架构师课程_汇总贴

    总目录: 第一课(2018.7.10) 01 mybatis框架整体概况(2018.7.10)- 转载于:https://www.cnblogs.com/wangjunwei/p/10424103.h ...

  8. JavaEE 企业级分布式高级架构师(十五)FastDFS分布式文件服务器(3)

    FastDFS学习笔记 Java操作FastDFS 测试文件上传 Spring Boot整合FastDFS 实现图片压缩 FastDFS主从文件 应用背景 解决办法 流程说明 Java代码 Nginx ...

  9. JavaEE 企业级分布式高级架构师(十七)ElasticSearch全文检索(5)

    ElasticSearch学习笔记 性能优化篇 ElasticSearch部署 选择合理的硬件配置--尽可能使用 SSD 给JVM配置机器一半的内存,但是不建议超过32G 规模较大的集群配置专有主节点 ...

最新文章

  1. Spring Cloud架构的各个组件的原理分析
  2. C++中四种类型转换符:static_cast、dynamic_cast、reinterpret_cast和const_cast要点解析
  3. 初始化JQuery方法与(function(){})(para)匿名方法介绍
  4. my.ini修改后服务无法启动_Spring Cloud Eureka 服务实现不停机(Zero-downtime)部署
  5. 想要轻松制作GIF图片,来看篇超全面的分析!
  6. DXperience 换肤
  7. 计算机控制的行业规模,2019年中国DCS控制系统行业市场现状及竞争格局分析,内资“两家独大”「图」...
  8. js—封装原生AJAX
  9. web测试之功能测试总结
  10. 【高等数学】微积分----教你如何简单地推导求导公式(二)
  11. 利用spring集成redis使用
  12. 写贺卡给毕业师姐怎么写计算机系的,[给师姐的毕业祝福语]对师姐的毕业祝福语...
  13. java web服务_如何用Java实现Web服务器
  14. 接口测试 — 使用Requests库发送POST请求
  15. 智慧交通系统平台建设方案(附下载)
  16. latex/texlive行超出正文(间)公式或文字超出。
  17. NUMA与英特尔下一代Xeon处理器学习心得
  18. 毛玻璃matlab,QA清单(毛玻璃赛题)
  19. 已经一点经纬度和距离,计算另一点的经纬度
  20. 用python做自动化控制-用 Python 自动化办公能做到哪些有趣或有用的事情?

热门文章

  1. 手机删除的视频怎么恢复?即刻扫描,快速恢复
  2. 3、使用angular cli初始化一个新项目
  3. Python的常用库
  4. 大数据舆情分析软件实时监控,TOOM大数据处理与舆情监控简介
  5. html5 canvas 圆圈,使用HTML5 Canvas arc()绘制圆形/圆环
  6. 【Python】强烈推荐的50个Pandas常用高级操作(建议收藏)
  7. 量化:通过ta-lib计算MA5指标
  8. ibatis例子(一)
  9. 鸿蒙大陆黑熊在哪,荒野大镖客2传说熊在哪捕获?传说熊捕获位置介绍
  10. 鼠标乱动原来是这个问题啊=.=