查询优化
查询优化:通过各种方案使SQL查询更高效
1)查询优化分为:逻辑优化(重写SQL)、物理优化(索引)
2)查询优化本质:降低查询语句所需的开销
查询开销指标:响应时间
、扫描行数
、返回行数
1)响应时间:排队时间和执行时间
2)扫描行数:为完成查询扫描数据行的总数
3)返回行数:真实返回数据行的总数
//尽量使返回行数接近于扫描行数,且行的访问代价并不一定相等
查询开销高的基本原因是:访问数据过多
1)重写查询:使用更优化的查询语句;
2)覆盖索引:将使用的列均设为索引,以避免回表;
3)修改表结构:修改表结构以增高查询效率(或添加辅助表);
查询流程
查询流程:客户端与MySQL服务器连接后,查询语句的执行流程
查询流程如下:
1)客户端发送查询语句请求至MySQL服务器;
2)检查是否命中缓存,命中则直接返回结果并结束查询;
3)对查询语句进行语法和语义分析,并解析成解析树并传递给预处理器优化;
4)优化器根据解析树生成优化后的执行计划,并传递给查询执行引擎;
5)查询执行引擎调用存储引擎API执行查询,并返回结果;
//MySQL 8.0之后的版本删除命中缓存查询(功能过于鸡肋)
如:MySQL服务器接收到客户端查询语句请求后的流程
//实际查询流程中可能并不按此线性流程执行(可能部分模块同时执行)
MySQL客户端和服务器之间通信协议为半双工
1)客户端只能用单个数据包将查询语句传递给服务器;
2)服务器用多个数据包将结果返回给客户端,且客户端必须完整接收结果;
//在客户端接收结果时,MySQL会对所有查询所使用的资源上锁
MySQL客户端通过线程连接服务器,服务器每刻都处于以下状态之一:
线程状态 | 说明 |
---|---|
Sleep | 等待客户端请求 |
Query | 正在执行查询 正在将结果发送给客户端 |
Locked | 等待锁 (锁的类型由存储引擎决定) |
Analyzing and statistics | 生成查询执行计划 (根据存储引擎统计信息) |
Copying to tmp table [on disk] | 正在执行查询 并将结果集复制到临时表 |
Sorting result | 正在对结果集进行排序 |
Sending data | 正在传送数据 正在生成结果集 正在将结果发送给客户端 |
查询缓存
查询缓存(Query Cache):将查询结果缓存并在其他查询流程开始前检测
1)查询缓存会在不同客户端之间共享;
2)本质:基于对大小写敏感的哈希查找实现;
3)查询命中查询缓存会直接返回缓存,但会再检查次用户权限;
//MySQL 8.X删除查询缓存(功能过于鸡肋)
查询缓存限制(失效):
1)查询语句必须完全相同:包括注释、大小写和空格数量;
2)不能包含函数和变量:无法确定其结果;
3)不适用频繁更新的表:缓存命中率较低;
解析器
解析器(Parser):通过关键词对查询语句进行语法/语义分析以生成解析树
1)语法分析:根据MySQL语法规则检测查询语句是否合法;
2)语义分析:根据MySQL关键词分析查询语句的语法;
3)解析树:承载查询语句以便后续优化和执行
预处理器会根据解析树做进一步处理:
1)预处理器根据MySQL规则检测解析树是否合法,并进行优化;
2)预处理器再次检测用户权限;
优化器
优化器(Optimizer):根据解析树生成执行计划
1)生成执行计划的同时会优化执行语句(逻辑优化,保证成本最小)
2)优化器决定的执行计划未必是最优的(优化器存在误判)
3)优化器分为:静态优化(编译时)、动态优化(运行时)
//SHOW STATUS LIKE ‘Last_query_cost’;可查最后一次查询成本
//优化器的估计成本并不一定是实际执行成本
优化器在以下情况会误判查询成本:
1)存在缓存:优化器不考虑任何层面的缓存;
2)并发查询:优化器不考虑任何并发执行的查询;
3)乐观评估:优化器不考虑任何不受控制的操作成本
4)统计信息不准确:优化器依靠存储引擎统计信息评估各查询成本;
常用场景
(1)重定义表的关联顺序
1)场景:表的实际关联顺序并总是按照查询语句指定的顺序进行;
2)优化:优化器会选择小表作为驱动表(减少外层循环次数);
(2)外连接变为内连接
1)场景:外连接并不总是按查询语句指定的方式连接;
2)优化:优化器会尽量使用内连接代替外连接,并优化内连接顺序;
(3)简化并规范表达式
1)场景:查询语句的表达式不简化和规范时
2)优化:优化器会使用等价的简化且规范表达式替换查询语句的表达式;
(4)转化为常数
1)场景:将查询语句中部分转化为常数
2)优化:优化器检测查询语句中表达式和语句是否可转换为常数;
(5)覆盖索引
1)场景:判断是否可调用覆盖索引
2)优化:优化器判断当前查询语句的列是否被索引列包含;
(6)转化子查询
1)场景:子查询并不总按照子查询方式进行
2)优化:优化器常使用等价的关联查询替代子查询
//子查询使用临时表存储数据,但临时表无法创建/使用索引(成本高)
(7)提前终止查询
1)场景:查询能提前终止
2)优化:优化器发现满足查询语句需求时,可立刻终止查询
(8)等值传播
1)场景:关联查询时列可互相获得对方WHERE条件
2)优化:优化器在等值关联时,将列的WHERE条件传递给其他列
HINT
优化器提示(HINT):控制优化器是否启用部分功能
(1)HIGH_PRIORITY
和LOW_PRIORITY
1)功能:指定语句的优先级(仅对使用表锁的存储引擎)
2)可用于SELECT、INSERT、UPDATE和DELETE语句
(2)DELAYED
1)功能:结果立刻返回客户端,将插入放入缓冲区(表空闲时再插入)
2)可用于INSERT和REPLACE语句
//会导致LAST_INSERT_ID()函数失效
(3)STRAIGHT_JOIN
1)功能1:查询语句中表只按照指定顺序关联(用于SELECT)
2)功能2:表的关联顺序不用被优化(用于JOIN)
3)可用于:SELECT和JOIN语句
(4)SQL_SMALL_RESULT和SQL_BIG_RESULT
1)功能:告知优化器是否将结果集放在内存的临时表中
2)可用于SELECT语句
(5)SQL_BUFFER_RESULT
1)功能:将结果集放入到内存的临时中再返回至客户端(尽快释放锁)
2)可用于SELECT语句
(6)SQL_CACHE和SQL_NO_CACHE
1)功能:是否将结果存储至查询缓存
2)可用于SELECT语句
(7)SQL_CALC_FOUND_ROWS
1)功能:使结果集尽可能包含更多的信息
2)可用于SELECT语句
(8)IGNORE INDEX和FORCE INDEX
1)功能:强制使用/不适用索引
2)可用于FROM语句
性能分析
EXPLAIN
EXPLAIN:分析指定语句以返回其执行计划
1)仅模拟执行指定语句,并非实际执行;
2)EXPLAIN不考虑缓存、优化器、触发器和存储过程的存在;
EXPLAIN分析格式:EXPLAIN 执行语句\G
1)也可使用“;”作为结束符,但\G增加可读性;
2)可在EXPLAIN后指定“FROMAT=JSON
”获取更多信息;
分析返回的分析数据字段含义如下:
字段 | 含义 |
---|---|
id | 执行语句的唯一ID |
select_type | 查询类型 |
table | 作用于的表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能使用的索引 |
key | 实际使用的索引 |
key_len | 实际使用的索引长度 |
ref | 与索引进行等值匹配的对象信息 |
rows | 估计读取的记录条数 |
filtered | 条件过滤剩余数据的百分比 |
Extra | 额外信息 |
(1)id:执行语句的唯一ID
1)若id相同则为同组,且执行顺序从上往下;
2)不同组中,id值越大其执行越优先;
//id应尽量少,代替其执行次数少
(2)select_type:查询类型
查询类型 | 说明 |
---|---|
SIMPLE | 简单查询 (未使用UNION) |
PRIMARY | 外层查询 |
UNION RESULT | 结果集用于联结 |
UNION | UNION的结果集 |
DEPENDENT UNION | 依赖其他查询的延迟查询 |
UNCACHEABLE UNION | 延迟查询且不缓存 |
SUBQUERY | 子查询中首查询 |
DEPENDENT SUBQUERY | 子查询中延迟查询 (依赖于其他查询) |
UNCACHEABLE SUBQUERY | 子查询且不缓存 |
DERIVED | 临时表查询 |
MATERIALIZED | IN条件的子查询 |
(3)table/type:作用于的表明、针对单表的访问方法
1)无论执行语句中包含多少表,其最终都为对单表的访问;
2)访问方法有以下12种(性能依次降低):
访问方法 | 含义 |
---|---|
system | 系统数据 (常通过内存获取) |
const | 常量数据 |
eq_ref | 主键或非空唯一索引等值扫描 |
ref | 非主键且非唯一的等值扫描 |
fulltext | 全文索引扫描 |
ref_or_null | 主键或唯一索引等值扫描 |
index_merge | 合并索引扫描 |
unique_subquery | 唯一索引子查询 |
index_subquery | 索引子查询 |
range | 范围扫描 |
index | 索引树扫描 |
ALL | 全表扫描 |
(4)Extra:额外信息
额外信息 | 说明 |
---|---|
Using filesort | 数据进行文件排序(FileSort) |
Using temporary | 使用临时表存储中间结果 |
Using where | 使用WHERE过滤数据 |
Using index | 使用覆盖索引 |
Using index condition | 部分数据命中索引进行IPC |
NOT EXISTS | NOT EXISTS替代IS NULL |
Zero limit | LIMIT 0时 |
Using join buffer(Block Nested Loop) | 子或关联查询时使用缓存优化 |
PROFILE
PROFILE:将MySQL上所有的执行语句相关信息记录到临时表
1)临时表按照执行顺序排序存储各个执行语句;
1)开启PROFILE记录(默认关闭):SET profiling='ON';
//可查询该变量验证是否开启
2)查看PROFILE记录:SHOW profiles;
//默认仅返回所有执行语句的总开销
3)查看最近一次查询的各个阶段的开销:SHOW profile 资源;
资源 | 说明 |
---|---|
ALL | 全部显示 |
CPU | 仅显示CPU相关 |
IPC | 仅显示接收/发送数据 |
SWAPS | 仅限交换次数 |
MEMORY | 仅显示内存 |
BLOCK IO | 仅显示IO |
PAGE FAULTS | 仅显示页面错误 |
CONTEXT SWITCHES | 仅显示上下文切换 |
//若省略资源,则仅显示各个阶段的总开销
//可在最后添加“FOR QUERY 数值N”以显示多个查询开销
慢查询
慢查询:将执行超过指定时间的操作记录到日志文件
1)慢查询日志的开销可忽略不计,但长期开启需部署日志轮转工具;
常用命令如下:
1)开启慢查询(默认关闭,临时)
SET GLOBAL slow_query_log='ON';
//也可通过修改配置文件slow_query_log实现永久开启
2)指定慢查询限定时间为N秒(可为小数)
SET GLOBAL long_query_time=数值N
//查看该变量可得知慢查询限定时间(默认10秒)
3)指定慢查询日志文件路径
SET GLOBAL long_query_time=数值N
//查看该变量可得知慢查询日志文件路径
4)返回已记录慢查询总数
SHOW STATUS LIKE 'slow_queries';
5)记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes='ON';
mysqldumpslow命令:分支慢查询日志文件
指令格式:mysqldumpslow 选项 慢查询日志文件路径
选项 | 含义 |
---|---|
-s | 以指定方式排序慢查询 |
-t N | 返回前N条慢查询 |
-g 正则表达式 | 正则表达式匹配含特定关键词的慢查询 |
-s选项有以下种排序方式:
排序方式 | 说明 |
---|---|
c | 访问次数 |
l | 锁定时间 |
r | 返回记录 |
t | 查询时间 |
al | 平均锁定时间 |
ar | 平均返回记录数 |
at | 平均查询时间 (默认方式) |
ac | 平均查询次数 |
优化方案
关联优化
关联查询的执行流程:
1)先在一个表中循环取出单条数据;
2)使用该单条数据到下个表中寻找匹配的数据行(嵌套循环执行该步骤);
3)根据各个表匹配的数据行,返回查询语句中所需的列;
4)在最后一个表中找所有匹配的数据行(找到则结束);
5)依次返回上层表尝试找到更多匹配的数据行;
尽量避免使用关联查询,单表查询获取结果集后在应用层中再进行关联
1)高扩展:更易对数据库做拆分
2)查询效率高:单表查询效率远高于关联查询
3)减少锁竞争:单表查询对锁的持有时间很短;
4)缓存效率高:避免部分表发生更新时缓存的失效;
5)减少冗余查询:应用层再关联可避免重复访问数据;
6)易实现哈希关联:避免使用MySQL的嵌套循环关联;
关联查询优化方案:
(1)左外连接时应选择小表作为驱动表,大表作为被驱动表
1)原因:减少外层循环次数
2)MySQL底层将所有右外连接改为等价的左外连接执行
(2)内连接时会自动将小结果集的表选为驱动表
1)原因:减少外层循环次数
(3)用含索引的字段作为连接字段
1)原因:保证数据检索时可使用索引
2)常在关联顺序中的第二个表的列上创建索引
(4)GROUP BY和ORDER BY语句只涉及一个表中的列
1)原因:保证可使用索引
排序优化
尽量避免进行排序(或避免对大量数据进行排序)
1)大量数据排序常会导致索引失效,只能进行FileSort
2)常需排序的数据可使用索引进行存储
FileSort:在磁盘中对数据进行排序
1)FileSort的排序方式分为:双路排序、单路排序
2)MySQL默认的排序方式为内存排序(只能用于小数据)
双路排序(慢):随机IO(两次IO)
1)扫描磁盘获取指针和ORDER BY列,并进行排序
2)根据排序结果集再次扫描磁盘获取数据
单路排序(快):顺序IO(一次IO)
1)从磁盘中读取所有列到内存中
2)根据ORDER BY列进行排序,依次输出数据;
排序优化方案:
(1)使用索引排序
1)避免WHERE语句使用全表扫描
2)避免ORDER BY语句使用FileSort排序
3)无法使用索引排序时,也需对FileSort调优
//提高sort_buffer_size和max_length_for_sort_data
(2)WHERE和ORDER BY使用的列相同时
1)使用单列索引(反之,多列索引)
(3)关联查询排序时避免ORDER BY语句的列全部来自第一个表
1)全部来自第一个表时,会在处理第一个表就进行FileSort
2)默认关联结果存储至临时表中,最后进行FileSort
//关联查询进行排序时无法使用索引排序(关联结果在临时表中)
其他优化
(1)查询仅返回需要的列
1)原因:避免返回全部列(无法使用覆盖索引)
2)强烈建议禁止SELECT *
(2)将重复查询的数据添加至缓存
1)原因:避免重复多次进行相同查询流程
(3)选择WHERE而非HAVING
1)原因:WHERE效率远高于HAVING
(4)WHERE条件中使用EXISTS()代替IN()进行子查询
1)原因:每增加一个IN()条件,优化器需做的组合将以指数形式增加
(5)UNION连接需排序/过滤时,先排序/过滤再合并
1)原因:合并使用的临时表无法使用索引(效率低)
(6)尽量使用UNION ALL进行关联
1)原因:对临时表做唯一性限制的代价过高
(7)GROUP BY语句避免WITH ROLLUP进行汇总
1)原因:导致使用FileSort或临时表(无法使用索引优化)
2)尽可能在应用层使用对结果集的汇总
(8)常用LIMIT语句限制返回数据
1)避免返回数据中过多无用数据和减少扫描的数据行
(9)关联查询代替子查询
1)原因:子查询使用临时表存储数据,但临时表无法创建/使用索引
//子查询可完成逻辑尚多个步骤的操作,但效率较低(避免使用)
(10)返回结果中只有一个表中的列时才使用关联子查询
1)原因:关联子查询的成本较高,只在特殊情况下使用;
(11)将大的DELETE语句切分成多个较小的
1)原因:尽可能较小地影响MySQL性能,同时减少MySQL复制的延迟
//且每次删除后暂停段时间(减少删除时锁的持有时间)