1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 高性能mysql读书笔记三性能查询优化

高性能mysql读书笔记三性能查询优化

时间:2019-05-16 15:19:29

相关推荐

高性能mysql读书笔记三性能查询优化

文章目录

1. sql执行生命周期1.1 客户端/服务端连接1.2 查询优化处理优化器作用优化策略1.3 统计数据1.4 mysql关联查询1.5 执行计划1.6 排序优化2. 优化数据访问2.1 查询数据过多2.2 扫描数据过多2.3 重构查询条件3. mysql相关不足3.1 EXISTS语法以及改进3.2 UNION 合并3.3 索引合并优化3.4 松散索引扫描3.5 最大最小值优化3.6 同时更新查找4. 查询优化器提示hint

1. sql执行生命周期

​ 想要对sql进行一些相关优化,我们需要了解查询sql的整个生命周期,sql的执行流程分成了如下图多个子任务,只要通过降低或者避免其中子任务的耗时,则整个sql性能就能得到提高。

客户端连接服务端并发送请求,执行之前还会做权限的判断。

在查询缓存中,看该SQL有没有执行过,如果有查询过,则把缓存结果返回。在MySQL内部一个查询缓存,查询缓存默认是不开启的。

因为查询缓存要求SQL和参数都要一样,所以这个命中率是非常低的。

没有开启查询缓存或没有命中,那么就到了解析器,解析器主要对SQL语法进行解析生成解析树。

验证sql的语法错误 关键字顺序,别名等sql语法规范。检查数据表和数据列是否存在验证权限

得到没有经过优化解析树之后,需要对这棵树进行预处理,得到优化后的解析数。比如常量放在什么地方,如果有计算的东西,把计算的结果算出来等等…

查询优化器:这一步是MySQL性能中,最关键的核心点,也是我们的优化原则.我们平时所讲的优化SQL。该优化器基于最低成本的执行计划。

SELECT * FROM USER WHERE USERNAME = toby AND PASSWORD = 1是先执行username = toby还是password = 1?每一条SQL的执行,查询优化器就是根据MySQL对数据统计表的一些信息,比如索引,数据量,会根据统计数据,进行一个综合的判定选择一个运行最快查询计划。最低成本:由于mysql自身的统计信息可能不准确的,MySQL根据这些信息选了一个它自认为最优的方案,但这个方案可能和我们想象的不一样。

执行计划会传给查询执行引擎,执行引擎选择存储引擎来执行查询计划(涉及到磁盘IO读取),获取对应的结果并返回。

如果开了查询缓存,则返回结果给客户端,并且查询缓存也放一份。

1.1 客户端/服务端连接

​ mysql客户端和服务端的连接是”半双工“通信,则每次客户端请求后,需要等待服务端将所有的数据返回给客户端才能结束。

同时每一个客户端与服务端的连接都有如下状态:

sleep: 服务端线程正在等待客户端发送请求。Query: 服务端线程正在执行查询/给客户端发送结果。locked: 服务端线程正在等待表锁。Analying and Statistics: 正在收集存储引擎的统计信息,并生成查询计划。Copying to tmp table [on disk]: 线程在执行查询并将结果复制到临时表中,如果含on disk 则说明在将临时表复制到磁盘中。Sorting result: 正在对结果集进行排序。Sending Data: 线程在多个状态传输数据、生成结果集、向客户端返回数据。

1.2 查询优化处理

优化器作用

对于一条查询有多种执行方式,优化器作用会基于成本(成本最低)选择一条‘最好’的执行计划。

-- 每当执行一个查询 可以通过查询当前会话的Last_query_cost获取其查询成本selecr * from #{tableName} where #{query}-- 获取当前查询成本SHOW STATUS LIKE "Last_query_cost"

​ 上述图显示 查询大概需要做12个数据页的随机查找才能完成。

多种原因会导致mysql优化器选择错误的执行计划

统计信息不准确执行估算成本等于真实的成本、不考虑并发影响我们期望时间短,优化器考虑成本最优

优化策略

静态优化:简单的代数、常量进行等价转换,编译时优化,一次优化,多次执行。

动态优化:每次执行根据数据量和索引优化执行。

重排关联表的顺序。

外链接转换为内连接

等价变幻 (a=5 and a>5 = a>=5)

count()、min()和max() B-tree 可以很快获取到min 和max 通过explain 为"select tables optimized away" 常数计算。

转换为常数表达式 explain: type = const

覆盖索引

自查询优化

提前终止查询 比如空结果、limit。

等值传播

select a.fid from a inner join b using(fid) where a.fid >500-- 等值传播select a.fid from a inner join b where a.fid > 500 and b.fid >500

IN()查找 等待于mysql的 多个等值查找 适合列的取值重复。

1.3 统计数据

统计信息由存储引擎实现,mysql生成查询执行计划时候从存储引擎获取统计信息,统计信息包括

表/索引占用的页数(页 存储引擎基本单元)

表的索引基数

基数又叫索引基数,是数据列所包含的不同值的数量。

例如,某个数据列包含值1、3、7、4、7、3,那么它的基数就是4

数据行/索引长度

数据行长度:每行数据长度限制

索引行长度:索引列长度限制

索引的分布情况

1.4 mysql关联查询

关联在mysql中并非指两个表以上的查询,而是每一次查询都是关联查询

Mysql关联执行的策略,任何关联查询均为嵌套循环关联操作,所谓嵌套循环为:现在一张表中取出符合条件的数据,循环取出数据查找下一个表匹配的行,以此类推最终找到所有匹配的行,根据行获取到对应的查找列。

for(int i =0 i<num;i++){for(int j = 0;j<num2;j++){for(int k = 0;k<num3;k++){//...以此往复}}}

union查询一系列的单个查询结果都放到一个临时表中,最终再从临时表获取完整数据。子查询有子查询先执行子查询将结果放入临时表,在将其当作普通表,在进行关联查询。

对于n表关联,则嵌套循环会有 n阶乘或者n-1阶乘关联顺序,关联顺序种类被称为执行计划的‘’搜索空间“,当搜索空间超过optimizer_search_depth的时候,优化器不会评估每一个关联顺序成本,而是使用贪婪搜索的方式获取最优关联顺序。

1.5 执行计划

mysql的执行计划是一棵包含全部查询的指令树,存储引擎执行这棵指令树并返回结果

-- 查看执行计划EXPLAIN EXTENDED ${querySql};SHOW WARNINGS;

# 强制不改变 查询条件为内连接的表关联select STRAIGHT_JOIN {querySql} STRAIGHT_JOIN与 JOIN 类似,只不过左表始终在右表之前读取。这可用于联接优化器以次优顺序处理表的那些(少数)情况。STRAIGHT_JOIN只适用于内连接,因为left join、right join已经知道了哪个表作为驱动表,哪个表作为被驱动表

1.6 排序优化

排序是一个成本很高的操作,文件排序会使用很大的临时存储空间,如果可能避免排序或者使用索引排序。 索引排序参考之前博文高性能mysql学习笔记二构建高性能索引

当不能使用索引排序,mysql需要自行进行排序,排序方式数据量是否超过“排序缓冲区”大小不同,如果不超过该阈值内存排序,否则磁盘排序,两者都统称为文件排序

磁盘排序:先将数据分开,对每个块使用快速排序,最终各个排好序的块进行合并。

排序算法

两次传输排序:第一次读取行指针和排序字段进行排序,再次根据排好序的结果再次读取记录第二次可能会产生大量随机IO),因为只读行指针和排序字段,可以让缓存区容纳更多数据进行排序。单次传输排序:读取所需要数据,根据字段进行排序最后返回排序结果,减少IO操作,缺点会占用大量的空间。

同时mysql排序需要使用大量的临时存储空间。

关联表的排序

所有排序字段均来自第一个表:extra字段为“using filesort”

非上述情况,mysql会先将关联的结果存放临时表,再进行文件排序,extra字段"using temporary;Using filesort"。

2. 优化数据访问

2.1 查询数据过多

查询数据不带分页但是只获取其中前n条数据关联查询或者查询了过多的列重复相同请求。

这个产生的性能问题属于低级错误,大概率开发不会犯这种问题。

2.2 扫描数据过多

是否扫描了过多的数据性能指标

响应时间:需要区分是排队时间(等待锁,等待资源) + 执行sql时间。

扫描的行数返回的行数: 通过EXPLAIN分析sql。主要看type字段和extra字段。

mysql性能分析 第二章节。

2.3 重构查询条件

复杂查询简化成多个简单查询(具体问题具体分析,衡量多个查询和一个复杂查询成本优劣)。

数据分治处理,比如批量更新、删除不必一次性全部执行,可以分成多个部分执行,减轻对服务器的压力,不必锁太多的数据。

分解关联查询

select * from tagleft join tag_post on tag.tag_id = tag_post.tag_idleft join post on post.post_id = tag_post.post_id where tag.name = "mysql"#转换为三个单表查询select * from tag where name = "mysql"select * from tag_post where tag_id in (xxx,xxxx)select * from post where post_id in (xxx,xxxx,xxxx,xxxx)

缓存,转换后的第一条sql 如果开启了缓存,则重复查询会直接获取到缓存。等值查询 后面的两个sql使用in可以使用多行等值查询。单表查询减少了锁的竞争。关联查询会造成同一条记录重复扫描。便于以后数据表拆分。

3. mysql相关不足

-- 语法示例SELECT 字段 FROM table WHERE EXISTS (subquery); -- 语法示例SELECT 字段 FROM table WHERE cloumn_name in (subquery); -- 例子SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.id = A.id); SELECT * FROM A WHERE EXISTS A.id in(SELECT b.id FROM B WHERE B.cloumn = ${query});

上述使用exists 和in语法涉及到了相关子查询,主表A的数据依赖内部子查询的结果。

EXISTS / NOT EXISTS 用于检查子查询是否至少会返回一行数据,该子查询实际上并不返回任何数据,而是返回布尔值true或false>

3.1 EXISTS语法以及改进

创建两张表,一张singer 歌手表,一张song 歌曲表,两表之间的关联关系为1对多。

DROP TABLE IF EXISTS `singer`;CREATE TABLE `singer` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `signer_id` int(11) DEFAULT NULL COMMENT 'qq音乐的主键', `signer_mid` varchar(50) DEFAULT NULL COMMENT 'qq音乐唯一标识', `full_name` varchar(50) DEFAULT NULL COMMENT '歌手全名', `english_name` varchar(50) DEFAULT NULL COMMENT '英文名', `short_name` varchar(50) DEFAULT NULL COMMENT '歌手简称(别名 多个)', `contract_id` varchar(10) DEFAULT NULL COMMENT '国籍', PRIMARY KEY (`id`) USING BTREE, KEY `IDX_SINGER_MID` (`signer_mid`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;DROP TABLE IF EXISTS `song`;CREATE TABLE `song` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `song_id` int(11) DEFAULT NULL COMMENT 'qq音乐id', `song_mid` varchar(50) DEFAULT NULL COMMENT 'qq音乐mid', `song_name` varchar(50) DEFAULT NULL COMMENT '歌曲名称', `song_type` varchar(5) DEFAULT NULL COMMENT '歌曲类型', `album_id` varchar(50) DEFAULT NULL COMMENT '所属专辑', `singer_mid` varchar(255) DEFAULT NULL COMMENT '所属歌手' PRIMARY KEY (`id`) USING BTREE, KEY `IDX_NAME` (`song_name`)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

场景如下:查找指定歌曲所属歌手,singer表中的singer_mid和song表中的name为普通索引

exists

SELECT si.full_name FROM singer siWHERE EXISTS (SELECT1 FROM song WHERE si.signer_mid = song.`singer_mid` AND song_name = "画");

执行顺序:

对该语句进行explain分析如下:先全表扫描singer表,在将扫描结果带入自查询进行song表的比较。

-- explain extended + show warnings 显示真正执行的sqlselect `utopia`.`si`.`full_name` AS `full_name`from `utopia`.`singer` `si` where exists(select 1 from `utopia`.`song` where (( `utopia`.`si`.`signer_mid` = `utopia`.`song`.`singer_mid`) and (`utopia`.`song`.`song_name` = '画')))

从真实的执行sql来看,mysql并未对该sql语句进行优化。exists不会被mysql优化。

首先执行一次外部查询,并缓存结果集,如 SELECT * FROM singer遍历外部查询结果集的每一行记录R,代入子查询中作为条件进行查询, SELECT 1

FROM. song WHERE si.signer_mid = song.singer_midAND song_name = “画”。如果子查询有返回结果,则EXISTS子句返回TRUE(NOT EXISTS 相反返回FALSE),这一行R可作为外部查询的结果行,否则不能作为结果。

in语句

SELECT si.full_name FROM singer si WHERE si.signer_mid IN (SELECTsinger_mid FROM song so WHERE so.song_name = "画")

explain 分析

select `utopia`.`si`.`full_name` AS `full_name` from `utopia`.`singer` `si`semi join (`utopia`.`song` `so`) where ((`utopia`.`so`.`song_name` = '画') and (`utopia`.`si`.`signer_mid` = `<subquery2>`.`singer_mid`))

Semi join 半连接查询: 检查一个结果集(外表)的记录是否在另外一个结果集(字表)中存在匹配记录,半连接仅关注”子表是否存在匹配记录”,而并不考虑”子表存在多少条匹配记录”,半连接的返回结果集仅使用外表的数据集,查询语句中IN或EXISTS语句常使用半连接来。

可能需要临时表存储数据,但是比上面的exists 性能要好很多。

join

SELECT si.full_nameFROM singer si INNER JOIN song soON si.signer_mid = so.`singer_mid` WHERE so.song_name = "画"

<b<explain分析

简单的关联查询,首先执行song 使用IDX_NAME索引,再执行singer表 使用singer_mid 索引查找,查找性能比上边的更好

3.2 UNION 合并

在union语句中mysql无法将外部的限制条件作用到每一条子句内,所以如果想两张表汇取出前20条,需要按照相同的排序条件和limit作用到每一个字据中

-- 耗性能的写法-- 会将A表和B表所有排序的数据汇总到临时表中再取出其中20条(SELECT A.name from A order by nameUNION ALLSELECT B.name from B order by name)limit 20;-- 正确的写法(SELECT A.name from A order by name limit 20 UNION ALLSELECT B.name from B order by name limit 20)limit 20;

3.3 索引合并优化

同一张表的 where中多个条件(或者join)进行AND/OR操作可能会使用到 index merge 技术。index merge:对多个索引分别进行条件扫描,然后将它们各自的结果进行合并(intersect/union),参考本人好友的文章索引合并优化

索引合并并交集访问算法(Index Merge Intersection Access Algorithm):对所有的索引同时执行扫描,扫描的结果取交集。

索引合并联合访问算法(Index Merge Union Access Algorithm):对所有的索引同时执行扫描,扫描的结果取并集。

select * from song where song_name ="潜力" OR singer_mid = "000P8peU0HhORi"

**索引合并排序联合访问算法(Index Merge Sort-Union Access Algorithm):**排序联合算法和联合算法之间的区别在于,排序联合算法必须首先获取所有行的行ID,然后对它们进行排序,然后再返回这些行。

EXPLAINselect * from song where song_name >"潜力" OR singer_mid = "000P8peU0HhORi"

3.4 松散索引扫描

mysql不持索引的松散(跳跃式)扫描,索引扫描需要先确定起点和终点,即使需要的数据很少也要扫描索引中每一条。且不支持hash索引并行执行

3.5 最大最小值优化

-- 如下字句会扫描全表 并获取最小主键值,其实如果使用主见扫描 查询第一条即为最小值select min(primaryKeCloumn) from table where noramCloumn = ${query} -- 调整如下 强制走主键扫描select min(primaryKeCloumn) from table USE INDEX(PRIMARY) where noramCloumn = ${query} limit

3.6 同时更新查找

mysql不支持对一张表同时进行查找和更新操作

UPDATE tb_testSET NAME = ( SELECT NAME FROM tb_test WHERE id= 2)WHERE `status` = 1-- 报错如下:-- You can't specify target table 'tb_test' for update in FROM clause,-- 可以将查询作为临时表进行操作UPDATE tb_testSET NAME = (select name from ( SELECT NAME FROM tb_test WHERE id = 2) as t)WHERE `status` = 1

4. 查询优化器提示hint

mysql的优化器并非万能,他不一定按照我们想要的结果来进行优化执行,所以mysql提供了**查询优化提示(Query Optimizer Hints)**让我们去引导优化器进行更好的优化。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。