大家好,我是烤鸭:
如何分析和调整MySQL查询以获得更好的性能
目录
MySQL中基于成本的查询优化
MySQL Optimizer mysql优化器
通常的想法:
将成本分配到操作
将成本分配到部分或替代计划
搜索成本最低的计划
基于成本的优化
访问方式
Subquery strategy 子查询策略
Join order 连接顺序
mysql优化的特点:
尽量减少CPU和I/O操作
使用简单的查询,尽量少用嵌套的查询
生成以左连接为主的线性查询执行计划
Optimizer Cost Model 优化器成本模型
Cost Estimates 成本估算
随机从硬盘获取"一页"的数据
默认 1.0
默认 1.0
默认 0.2
默认 0.1,mysql 5.7以后可配置
Cost Model Example 成本模型实例
全表扫描
IO成本:表中的pages * IO阻塞读取成本
CPU成本: 行 * 行计算成本
范围扫描(第二索引)
IO成本:范围中的行 * IO阻塞读取成本
IO成本:范围中的行 * 行计算成本
用于监视,分析和调整查询的工具
付费产品
执行计划,MySQL 系统计划
MEM 图形化界面,收费的,暂时不考虑了,看下面的例图
Performance schema 执行计划
一些有用的表:
events_statements_history,events_statements_history_long Most
recent statements executed
大部分最近执行的statement
events_statements_summary_by_digest Summary for similar statements
(same statement digest)
总结相似操作(相同的statement合并)
file_summary_by_event_name
Interesting event: wait/io/file/innodb/innodb_data_file
table_io_waits_summary_by_table
table_io_waits_summary_by_index_usage
Statistics on storage engine access per table and index
统计存储引擎的每个表和索引
Statement events
Tables:(Current statement for each thread)
每个线程当前执行的statement
events_statements_history (10 most recent statements per thread)
每个线程最近执行的最多10条statement
events_statements_history_long (10000 most recent statements)
最近执行的最多10000statement
Statement digest statement合并
Normalization of queries to group statements that are similar to be
grouped and summarized:
规范化的按statement分组的查询,类似于分组和汇总、如下:
SELECT * FROM orders WHERE o_custkey=10 AND o_totalprice>20
SELECT * FROM orders WHERE o_custkey = 20 AND o_totalprice > 100
SELECT * FROM orders WHERE o_custkey = ? AND o_totalprice > ?
events_statements_summary_by_digest
MySQL sys Schema 系统计划
一组视图,过程和函数,旨在简化读取原始性能模式数据
实现许多常见的DBA和Developer用例
每个用户的文件IO使用
哪个索引从未被使用
哪些查询是全表扫描
非常有用的函数示例
包含mysql 5.7
与MySQL Workbench捆绑在一起
MySQL sys Schema MySQL 系统计划
SELECT * FROM sys.statement_analysis LIMIT 1\G
)列出带聚合的规范化语句视图统计信息,按每个规范化语句的总执行时间排序
EXPLAIN Understand the query plan
Structured EXPLAIN (EXPLAIN FORMAT=JSON SELECT …
)
Contains more information:
触发索引的条件
成本估算
数据估算
Visual EXPLAIN 可视化EXPLAIN
Optimizer Trace: Query Plan Debugging 优化程序跟踪:查询计划调试
展示了选择的计划
优化跟踪展示了为什么选这个计划
SELECT * FROM t1,t2 WHERE f1=1 AND f1=f2 AND f2>0;
SELECT trace FROM information_schema.optimizer_trace INTO OUTFILE LINES TERMINATED BY '';```
SET optimizer_trace="enabled=off";
数据访问和索引选择
找到最合适的方式从储存引擎读取数据
对于每个表,找到最佳访问方法
检查访问方法是否有用
估算使用访问方法的成本
选择最便宜的使用
访问方法的选择是基于成本的
参考访问
单表查询
Join Queries, continued 未命中的索引的连接查询
范围优化器
目的: 找到需要读取索引的"最小"变化
SELECT * FROM t1 WHERE (key1 > 10 AND key1 < 20) AND key2 > 30
范围优化器,常量
范围优化器选择WHERE条件"有用的"部分
条件是一列和常量比较的数据
嵌套的AND/OR 是支持的
结果: 没有交集的范围从索引读取
成本估算基于每个范围的记录数量
记录估算通过询问存储引擎("指数潜水")获取
Optimizer Trace show ranges 优化程序跟踪显示范围
Range Optimizer: Case Study 范围优化:案例学习
为什么全表扫描?
SELECT * FROM orders WHERE YEAR(o_orderdate) = 1997 AND MONTH(o_orderdate) = 5
AND o_clerk = 'Clerk#000001866';
一些未使用索引的原因
ndexed column is used as argument to function 索引列使用函数计算
YEAR(o_orderdate) = 1997
Looking for a suffix 寻找前缀
name LIKE '%son'
First column(s) of compound index NOT used 符合索引的前置列未被使用
b = 10 when index defined over (a, b) 当复合索引(a,b),但是查询条件是b = 10
Type mismatch 类型不匹配
my_string = 10
Character set / collation mismatch 字符集/排序规则不匹配
t1 LEFT JOIN t2 ON t1.utf8_string = t2. latin1_string
案例学习:
重写查询以避免索引列上的函数
SELECT * FROM orders WHERE o_orderdate BETWEEN '1997-05-01' AND '1997-05-31' AND o_clerk = 'Clerk#000001866';
i_o_orderdate 命中索引
添加另一个索引
mysql> CREATE INDEX i_o_clerk ON orders(o_clerk); 就上面的例子,添加 o_clerk 索引
Range Access for Multi-Column Indexes 多列索引的范围访问
Example table with multi-part index 具有多部分索引的示例表 有索引abc ,index(a,b,c)
Logical storage layout of index: 索引的逻辑存储布局:
Range Access for Multi-Column Indexes, cont 多列索引的范围访问,常量
Equality on 1st index column?第一个索引列上的平等?
Can add condition on 2nd index column to range
condition?可以在第二个索引列上添加条件到范围条件?
例如:
SELECT * from t1 WHERE a IN (10,11,13) AND (b=2 OR b=4)
Resulting range scan 结果范围扫描:
Non-Equality on 1st index column? 第一个索引列上不平等?
Can NOT add condition on 2nd index column to range
condition?可以不在第二个索引列上添加条件到范围条件?
例如:
SELECT * from t1 WHERE a > 10 AND a < 13 AND (b=2 OR b=4)
Resulting range scan 结果范围扫描:
案例学习:
创建多列索引
CREATE INDEX i_o_clerk_date ON orders(o_clerk, o_orderdate);
Performance Schema: Query History 执行计划:查询历史
mysql5.7 默认enabled的。
UPDATE performance_schema.setup_consumers SET enabled='YES' WHERE name = 'events_statements_history';
SELECT sql_text, (timer_wait)/1000000000.0 "t (ms)", rows_examined rows
FROM performance_schema.events_statements_history ORDER BY timer_start;
Index Merge 索引合并
同一张表使用多重索引
实施索引合并策略
索引合并并集
不同的索引之间使用OR条件
索引合并交集
不同的索引之间的情况
索引合并排序并集
WHERE范围条件使用OR
Index Merge Union 索引合并并集
单一索引不能处理不同列使用OR的情况
SELECT * FROM t1 WHERE a=10 OR b=10
INDEX(a) a = 10 INDEX(b) b = 10
Result: a= 10 OR b = 10
Index Merge Intersection 索引合并交集
在AND条件下组合多个索引以减少(或避免)对基表访问次数
SELECT * FROM t1 WHERE a=10 AND b=10
INDEX(a) a = 10 INDEX(b) b = 10
Result: a= 10 AND b = 10
Example1:
Example2:
Beware of low-selectivity indexes! 注意低选择性索引!
Example3:
Handler status variables 处理程序状态变量
连接优化器
”Greedy search strategy” 贪婪的搜索策略
目的: Given a JOIN of N tables, find the best JOIN ordering N张表中找到最好的连接排序
从所有1表计划开始(根据大小和密钥依赖性排序)
用剩余的表扩展每个计划
深度优先
如果"部分计划成本" > "最好的计划成本"
精简计划
探索式精简
精简没什么用的部分计划
可能在极少数情况下错过最佳计划 (关闭并设置 optimizer_prune_level = 0)
JOIN Optimizer Illustrated 连接优化器插图
Change join order with STRAIGHT_JOIN 使用STRAIGHT_JOIN更改连接顺序
Join Order 连接顺序
Join Order Hints 连接顺序提示
MySQL 8.0 Optimizer Labs Release MySQL 8.0优化工具实验室发布
对此查询具有相同效果的替代方案
JOIN_PREFIX(customer) JOIN_SUFFIX(orders) JOIN_FIXED_ORDER()
National Market Share Query 全国市场份额查询
SELECT o_year, SUM(CASE WHEN nation = 'FRANCE' THEN volume ELSE 0 END) / SUM(volume) AS mkt_share
FROM (
SELECT EXTRACT(YEAR FROM o_orderdate) AS o_year, l_extendedprice * (1 - l_discount) AS volume, n2.n_name AS nation
FROM part
JOIN nation n2 ON s_nationkey = n2.n_nationkey JOIN region ON n1.n_regionkey = r_regionkey JOIN nation n1 ON c_nationkey = n1.n_nationkey JOIN customer ON o_custkey = c_custkey JOIN orders ON l_orderkey = o_orderkey JOIN supplier ON s_suppkey = l_suppkey JOIN lineitem ON p_partkey = l_partkey WHERE r_name = 'EUROPE' AND o_orderdate BETWEEN '1995-01-01' AND '1996-12-31' AND p_type = 'PROMO BRUSHED STEEL'
) AS all_nations GROUP BY o_year ORDER BY o_year;
MySQL Workbench Visual EXPLAIN MySQL Workbench 可视化 EXPLAIN
Force early processing of high selectivity conditions 强制早期处理高选择性条件
Improved join order 优化连接顺序
mysql 5.7 对Query8的优化
考虑对非索引列进行过滤
部分表强制提前处理无需提示
将派生表合并到外部查询中
无临时表
子查询
Overview of Subquery Optimizations 子查询优化概述
Subquery category: 子查询分类 |
Strategy:策略 |
---|---|
IN (SELECT …) | Semi-join 半连接 Materialization 实体化 |
NOT IN (SELECT …) | IN ➜ EXISTS |
FROM (SELECT …) | Merged 合并 Materialized 实体化 |
ALL/ANY (SELECT …) | MAX/MIN re-write 最大/最小化 重写 |
EXISTS/other | Execute subquery 执行子查询 |
IN子查询的传统优化
IN 转化为 EXISTS
从IN子查询转化为EXISTS子查询通过"向下"的同等子查询
SELECT title FROM film WHERE film_id IN (SELECT film_id FROM actor WHERE name=“Bullock”)
优化为 =>SELECT title FROM filmWHERE EXISTS (SELECT 1 FROM actor WHERE name=“Bullock” AND film.film_id = actor.film_id)
优点:子查询将计算更少的记录
注意:如果"向下"表达式为NULL,则可特殊处理
Semi-join 半连接
Convert subquery to inner join, BUT Need some way to remove duplicates
将子查询转换为内连接 但需要一些方法去重
Different strategies for duplicate removal: 去重的不同策略
匹配优先(等价于IN—>EXISTS的方式)
懒扫描(索引扫描,跳过重复)
实体化:MatLookup(像子查询实体化),MatScan(实体化表在连接顺序的第一位)
去重(用唯一索引将半连接的行插入临时表;重复列将会被拒绝。无论连接顺序)
If duplicate removal is not necessary: 如果去重是非必须的话
表将删掉
Main advantage : 主要优势:
有更多优化"连接顺序"的选择
SELECT o_orderdate, o_totalprice FROM orders WHERE o_orderkey IN (SELECT l_orderkey FROM lineitem WHERE l_shipDate='1996-09-30');
使用行代替排序会经过较少的行
如果子查询包含union(并集)或者aggregation(聚合)不能使用半连接
MySQL 5.6: Semi-join: Example mysql5.6 半拦截的例子:
MySQL 5.7: Hint Example: SEMIJOIN 提示示例: 半连接
没有提示,优化器选择半连接算法LooseScan
使用提示禁用半连接:
子查询实体化
执行一次子查询并在临时表中存结果
表有唯一索引可以快速查找并去重
执行外部查询并检查临时表中的匹配项
图 Subquery Materialization
比较子查询实现和IN➜EXISTS 比较IN —> EXISTS的实现
Derived Tables 派生表
FROM子句中的子查询
SELECT AVG(o_totalprice) FROM ( SELECT * FROM orders ORDER BY o_totalprice DESC LIMIT 100000 ) td;
MySQL 5.6及更早版本:单独执行并将结果存储在临时表中(实现)
MySQL 5.7:处理类似于视图的派生表:可以与外部查询块合并
用外部连接合并派生表
基于GROUP BY,DISTINCT,LIMIT或聚合函数的派生表将不会合并
Hint: Merge/Materialize Derived Table or View 暗示:合并/实现派生表或视图
MySQL 8.0.0优化器实验室发布
Derived tables/views are, if possible, merged into outer query 如果可能,派生表/视图将合并到外部查询中
NO_MERGE hint can be used to override default behavior: NO_MERGE提示可用于覆盖默认行为
:
SELECT /*+ NO_MERGE(dt) */ * FROM t1 JOIN (SELECT x, y FROM t2) dt ON t1.x = dt.x;
MERGE hint will force a merge MERGE提示将强制合并
SELECT /*+ MERGE(dt) */ * FROM t1 JOIN (SELECT x, y FROM t2) dt ON t1.x = dt.x;
Can also use MERGE/NO_MERGE hints for views 也可以使用MERGE / NO_MERGE提示查看视图
SELECT /*+ NO_MERGE(v) */ * FROM t1 JOIN v ON t1.x = v.x;
排序
ORDER BY Optimizations 排序优化
通常的解决方案;"文件排序"
在排序之前将查询结果存储在临时表中
如果数据量很大,可能需要在磁盘上使用中间存储进行多次传递排序
优化
利用索引按排序顺序生成查询结果
对于"LIMIT n查询,保留内存中n个顶级项的优先级队列而不是文件排序。 (MySQL 5.6)
Filesort 文件排序:
Status variables 状态变量
Performance Schema 执行计划
图 Filesort Performance Schema
Sorting status per statement available from Performance Schema可从执行计划中对每个语句进行排序
案例1
案例2
案例3
图 Filesort Case Study3
Increase sort buffer增加排序缓冲区
SET sort_buffer_size = 1024*1024
默认256 KB。
案例4
图 Filesort Case Study4
Increase sort buffer even more (8MB)进一步增加排序缓冲区 (8MB)
SET sort_buffer_size = 8*1024*1024;
图 Using Index to Avoid Sorting
Using Index to Avoid Sorting使用索引来避免排序
图 Using Index to Avoid Sorting Case study
Case study revisited重新研究案例
影响优化器
添加索引
强制使用特定的索引
使用索引,强制索引,忽略索引
强制特定的关联顺序
调整会话变量
MySQL 5.7: New Optimizer Hints MySQL 5.7:新的优化器暗示
Ny hint syntax:暗示语法:
SELECT /*+ HINT1(args) HINT2(args) */ … FROM …
New hints: 新的暗示
:
批量key访问
Block Nested-Loop (BNL) 阻塞嵌套循环算法
(表的索引)
索引命中情况下推
索引情况下该范围没有可优化的
Query Block 查询阻塞
Finer granularilty than optimizer_switch session variable 比optimizer_switch会话变量更精细
Optimizer Hints
Optimizer Labs 8.0.0 版本中的新暗示
启用/禁用视图和派生表的合并
连接顺序
JOIN_ORDER(tables)
JOIN_PREFIX(tables)
JOIN_SUFFIX(tables)
JOIN_FIXED_ORDER()
考虑添加的暗示:
强制/忽略index_merge替代方案
重新实现新语法中的索引暗示
暂时为一个查询设置会话变量
MySQL 5.7: Query Rewrite Plugin 查询重写插件
Rewrite problematic queries without the need to make application
changes 无需更改应用程序即可重写有问题的查询
Add hints 添加暗示
更新连接顺序
更多
Add rewrite rules to table: 向表中添加重写规则:
新的解析前和解析后查询重写API
MySQL 5.7: Adjustable Cost Constants 可调成本常量
Experimental! Use with caution! No guarantees 实验! 谨慎使用! 不保证!!
官方博客
作者博客
优化团队博客
论坛