✓ 优化的目的是让资源发挥价值;
✓ SQL和索引是调优的关键,往往可以起到“四两拨千斤”的效果。
✓ 充分了解核心指标,并构建完备的监控体系,这是优化工作的前提;
✓ SQL优化的原则是减少数据访问及计算;
✓ 常用的优化方法主要是调整索引、改写SQL、干预执行计划。
SQL 查询关键资源指标
数据扫描、显式计算
每秒 IO 请求次数
物理读写关键资源指标
吞吐量
业务压力
应用配置
执行效率
反映整体查询效率的引擎指标
导致SQL执行效率下降的特殊行为
知道了我们关注的指标,接下来就是定点监控,看看究竟是哪个/哪些指标影响了应用系统的性能,这样就可以定点排查问题,制定具体的优化策略与方法。
数据存取是数据库系统最核心功能,所以IO是数据库系统中最容易出现性能瓶颈,减少SQL访问IO量是SQL优化的第一步;数据块的逻辑读也是产生CPU开销的因素之一。
减少访问量的方法:创建合适的索引、减少不必访问的列、使用索引覆盖、语句改写。
计算操作进行优化也是SQL优化的重要方向。SQL中排序、分组、多表连接操作等计算操作都是CPU消耗的大户。
减少SQL计算操作的方法:排序列加入索引、适当的列冗余、SQL拆分、计算功能拆分。
✓ InnoDB 的表是典型的 IOT,数据本身是 B+ tree 索引的叶节点。
✓ 扫描二级索引可以直接获取数据,或者返回主键 id;
✓ 优化器是数据库的大脑,我们要了解优化器,并观测以及干预 MySQL 的行为。
(9) SELECT
(10) DISTINCT column,
(6) AGG FUNC(column or expression), ...
(1) FROM left tab1
(3) JOIN right tab2
(2) ON tab1.column = tab2.column
(4) WHERE constraint_expression
(5) GROUP BY column
(7) WITH CUBE ROLLUP
(8) HAVING constraint_expression
(11) ORDER BY column ASC|DESC
(12) LIMIT count OFFSET count;
➢ explain
[extended] SQL_Statement
➢ show variables
like ‘optimizer_switch’
show [full] processlist
information_schema.processlist
copy to tmp table
:出现在某些alter table语句的copy table操作
Copying to tmp table on disk
:由于临时结果集大于tmp_table_size,正在将临时表从内存存储转为磁盘存储以此节省内存
converting HEAP to MyISAM
:线程正在转换内部MEMORY临时表到磁盘MyISAM临时表
Creating sort index
:正在使用内部临时表处理select查询
Sorting index
:磁盘排序操作的一个过程
Sending data
:正在处理SELECT查询的记录,同时正在把结果发送给客户端
Waiting for table metadata lock
: 等待元数据锁
…
Using index
:MySQL 直接通过索引返回有序记录,不需要额外的排序操作,操作效率较高;
Using filesort
:无法只通过索引获取有序结果集,需要额外的排序,某些特殊情况下,会出现 Using temporary。
尽量通过索引来避免额外的排序,减少CPU资源的消耗。
✓ where 条件和 order by 使用相同的索引
✓ order by 的顺序和索引顺序相同
✓ order by 的字段同为升序或降序
注意: 当 where 条件中的过滤字段为覆盖索引的前缀列,而 order by 字段是第二个索引列时,只有 where 条件是 const 匹配时,才可以通过索引消除排序,而 between…and 或 >?、 这种 range 匹配 都无法避免 filesort 操作。
当无法避免filesort操作时,优化思路就是让filesort的操作更快。
✓ 两次扫描算法: 两次访问数据,第一步获取排序字段的行指针信息,在内存中排序,第二步根据行指针获取记录;
✓ 一次扫描算法: 一次性取出满足条件的所有记录,在排序区中排序后输出结果集,是采用空间换时间的方式。
注: 需要排序的字段总长度越小,越趋向于第二种扫描算法,MySQL通过 max_length_for_sort_data 参数的值来进行参考选择。
✓ 适当调大 max_length_for_sort_data 这个参数的值,让优化器更倾向于选择第二种扫描算法;
✓ 只使用必要的字段,不要使用 select * 的写法
✓ 适当加大 sort_buffer_size 这个参数的值,避免磁盘排序的出现(线程参数,不要设置过大
)
➢ 子查询会用到临时表,需尽量避免
➢ 可以使用效率更高的 join 查询来替代
✓ 等价改写、反嵌套
如下SQL:
select * from customer where customer_id not in (select customer_id from payment);
改写成:
select * from customer a left join payment b on a.customer_id = b.customer_id where b.customer_id is null;
➢ 分页查询,就是将过多的结果在有限的界面上分好多页来显示;
➢ 其实质是每次查询只返回有限行,翻页一次执行一次。
✓ 消除排序
✓ 避免扫描到大量不需要的记录
SQL场景(film_id为主键):
# 此时 MySQL 排序出前 10020 条记录后仅仅需要返回第 10001 到 10020 条记录,前 10000 条记录造成额外的代价消耗
select film_id,description from film order by title limit 10000,20;
优化策略1:覆盖索引:
alter table film add index idx_lmtest(title,description);
✓ 记录直接从索引中获取,效率最高
✓ 仅适合查询字段较少的情况
优化策略2:SQL改写
select a.film_id,a.description from film a inner join (select film_id from film order by title limit 1000,20) b on a.film_id=b.film_id;
✓ 优化的前提是 title 字段有索引
✓ 思路是从索引中取出 20 条满足条件记录的主键值,然后回表获取记录
处理策略:
✓ and 子句多个条件中拥有一个过滤性较高的索引即可
✓ or 条件前后字段均要创建索引
✓ 为最常用的 and 组合条件创建复合索引
嵌套循环连接算法
for each row in t1 matching range {
for each row in t2 matching reference key {
for each row in t3 {
if row satisfies join conditions, send to client
}
}
}
Hash Join
✓ 每层内部循环仅获取需要关心的数据;
✓ 引申算法:Bloack Nested-Loop。
✓ 减少循环次数;
✓ 小表:返回结果集较少的表。
-- 如批量插入语句:
insert into test values(1,2,3);
insert into test values(4,5,6);
insert into test values(7,8,9);
...
-- 可改写为如下形式:
insert into test values(1,2,3),(4,5,6),(7,8,9) ...;
通过 LOAD DATA INFILE 句式,从文本装载数据,通常比 insert 语句快 20 倍。