高性能mysql-查询性能优化-笔记
衡量查询性能的三个指标
- 返回的行数:意思是select返回的列数以及行记录数。
- 扫描的行数:查询所需要扫描的行数。
- 相应的时间:执行sql时间以及等待(sleep等待连接,lock等待锁的时间)
返回的行数
案例
1. 查询不需要的记录即多余的记录
2. 查询多余的列。很多时候为了方便直接select *,这样造成有些字段是不需要的也查询出来了。建议只查询出所需的字段。减少返回数据的大小,降低IO的压力
3. 重复查询相同的数据
优化方法
1. 采用索引覆盖以及延时索引
2. 重写查询
采用复杂查询还是简单查询
切分查询:将大的查询切分成小的查询。例如,delete数据多时,会造成锁住很多数据,占满了事务日志,造成后续的查询等待锁时间过长。如果切分成小的查询,减少了锁的竞争以及减少了对性能的影响。
分解关联查询:将关联查询分解成多条简单查询。在过去会认为客户端连接性能较低,且解析优化的过程较长,所以一般会把多条简单的查询写成关联查询。但现在随着连接性能提高以及缓存效率的提高,将关联查询分解成多条简单查询,未尝不是件好事。分解后的优势在于:1.增加利用查询的缓存。2.减少了锁竞争。3.减少冗余数据的查询。4.扩展性提高
3.更改库表结构:
例如:采用单独的汇总表
扫描的行数
案例
1. 减少扫描的行数:例如当只查询一条数据记录时,若没有提示存储引擎,则扫描到了对应的记录数据后,还会继续扫描知道结束。解决办法是加上limit 1,提示只需要一条数据记录,就不会对继续扫描多余的行数。
响应时间
响应时间由等待时间和执行时间组成。要想了解时间都用在哪里,首先要知道执行语句过程中有哪些步骤。根据mysql逻辑结构分成三层,第一层是客户端与数据库的连接,第二层是服务器层,有解析、缓存、优化,第三层是存储引擎,负责数据的存储与读取。接下来会根据三层结构分析执行查询中有哪些步骤?
查询流程
- 客户端与服务区进行连接通信
- 服务器检查是否有缓存,缓存如果命中则,直接返回缓存中的结构。否则进行下一步
- 对sql进行解析预处理,再由优化器生成执行计划
- 根据执行计划,服务器调用存储引擎的API查询
- 得到数据则缓存并返回给客户端
查询状态
对于一个mysql连接,或者说一个县城,任何时刻都一个状态,该状态表示MYSQL当前正在做什么。有很多方式能查看当前的状态,最简单的是SHOW FULL PORCESSLIST命令。
- Sleep
线程等待客户端发送新的请求 - Query
线程正在查询或者正在将结果发给客户端 - Locked
线程等待锁释放 - Analyzing and statistics
线程正在收集存储引擎的统计信息 - Copying to tmp table
线程正在执行查询,并且将其结果集都复制到一个临时表中,这种状态一般要么是在做Group By操作,要么是文件排序操作,或者是UNION操作。若后面还有on disk标记,则表示临时表放在磁盘中 - Sorting result
线程正在对结果进行排序 - Sending data
表示多种情况,可能在多个状态之间发传输数据,或者在生成结果集,或者在向客户端返回数据
优化特定的查询
优化COUNT()查询
很多认为MYISAM存储引擎中的count(*)肯定比INNODB存储引擎中的count(*)快,其实不一定,当在无where条件的情况下,MYISAM中存储了count(*)的总数据数,所以这种时候MYISAM比INNODB快。但是在有where条件下的时候,可以是统计某个列值的数量时,MYISAM的COUNT()与其他引擎没有什么不同。
如果当要统计某个列值的数量时,建议直接count(*),而不是count(列字段)。这样的好处在于直接告诉存储引擎忽略所有列而直接统计所有行数。
使用近似值,如果总记录数不要求完全精确的话,比如统计网站当前活跃用户数以及搜索引擎的总条数。这样我们可以利用explain中的rows行数进行代替。
关联查询
- 优化关联查询,要确保ON或者USING子句中的列上有索引
- 建立索引时需要考虑到关联的顺序。通常来说,只需要在关联顺序中的第二个表的相应列上创建索引。例如,当表A和表B用列c关联的时候,假设关联的顺序是B、A,那么就不需要在B表的c列上建立索引。没有用到的索引只会带来额外的负担。
- 确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样才能使用索引来优化这个过程
子查询
优化的建议尽可能的使用关联查询代替子查询
GROUP BY 和 DISTINCT
- 对关联字段做分组效率高
- WITH ROLLUP转移到应用程序中进行处理
LIMIT分页
当翻页到非常靠后页面的时候,偏移量会非常大,这是LIMIT的效率是非常差的。例如查询第50000,50020记录时,需要扫描50020条记录后返回结果,这样的效率是非常低的。优化方法如下:
- 利用上一页最后的主键做偏移。例如上述的例子,必须满足的是主键是连续且
SELECT * FROM sakila.rental where rental_id > 50000 DESC LIMIT 20;
- 延时关联。利用索引覆盖先查找出主键,在通过主键查找对应的记录。尽可能的减少扫描的页面
SELECT film.film_id, film.description FROM sakila.film
INNER JOIN
(SELECT film_id FROM sakila.film ORDER BY title LIMIT 50000,20) AS lim
USING(film_id);
返回的记录总数
- 将总数替换成下一页按钮。假设每页显示20条记录,每次查询就增多一条即21条记录,如果有21条记录,则显示下一页,否则就说明没有更多的数据,无须显示下一页按钮
- 将指定数量的数据缓存
- 利用执行计划explain中的row
UNION查询
除非确实需要服务器消除重复的行,否则一定要使用UNION ALL。如果没有ALL关键字,MySql会给临时表加上DISTINCT选项,这会导致对整个临时表的数据做唯一性检查。这样做的代价非常高。