优化数据访问
查询性能低下的最基本原因就是访问了太多数据,一些查询要不可避免地筛选大量的数据,大部分性能欠佳的查询都可以用减少数据访问的方式进行优化。
查询不需要的数据
首先分析应用程序是否正在获取超过需要的数据,这通常表现在获取了过多的行或列。一些查询先向服务器请求不需要的数据,再丢掉他们,这个让服务器造成了额外的负担,增加了网络开销,消耗了内存和CPU资源。如果前台只需要显示15条数据,而你的查询结果集返回了100条,则要想想是否真有必要这样干了,最好使用LIMIT来限制查询的条数。
总是取出全部列
尽量避免使用SELECT *
,通常你并不需要所有的列,获取所有的列将会造成覆盖索引这样的优化手段失效,也会增加磁盘I/O、内存和CPU的开销等,所以基于这种情况,尽量使用SELECT t.id,t.name...
这种查询具体字段的SQL。
重复查询相同的数据
可以在初次查询的时候将数据缓存起来,需要的时候再从缓存中取出。
msyql是否存在额外的扫描
衡量查询开销的三个指标:
- 响应时间
- 扫描的行数
- 返回的行数
利用explain + sql语句
可以查看相应的指标。
根据索引查询,MySQL 能快速到达一个位置去搜寻数据文件的中间,没有必要看所有数据。
mysql> explain select * from emp where id>10 and id<17;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | range | id | id | 4 | NULL | 6 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
如果不使用索引,MySQL 必须从第1条记录开始然后读完整个表直到找出相关的行。表越大,花费的时间越多。
mysql> explain select * from emp where age>20 and salary < 10000;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | emp | NULL | ALL | NULL | NULL | NULL | NULL | 18 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
重构查询方式
一个复杂查询还是多个简单查询
平时我们更倡导用尽可能少的查询做尽可能多的事情,这样可以减少网络通信开销,能减少查询解析和优化的步骤,以及代码上似乎更优雅。但是在MySql中,MySql被设计成可以很高效地连接和断开服务器,而且能很快地响应精简的查询。在现代网络下,MySql在一般的服务器上每秒钟可以处理50000个查询。因此,对于一些耗时的复杂查询,可以通过分解查询以得到更高的效率。
分解查询
把一个多表联接分解成多个单表查询,然后在应用程序端实现联接。
例如:
有如下的一个连接查询:
SELECT * FROM tag JOIN tag_post ON tag.id = tag_post.tag_id WHERE tag.title = 'test';
分解成两个查询,类似于子查询:
SELECT * FROM tag WHERE tag.title = 'test';
假设返回id有 (10,11,12,13,14,15);
SELECT * FROM tag_post WHERE tag_id IN (10,11,12,13,14,15)
;
优点:
- 1、缓存的性能更高:上面的查询已经被缓存起来,下次再查询
tag.title='test'
,则会直接从缓存中取出;第二条IN操作,下次查询(11,12,14,20,25)
,对于11,12,14则直接从缓存中取出,只去读取20,25。如果一个表经常改变,分解联接可以减少缓存失效的次数。 - 2、可以减少多余的行访问,联接操作,每从tag表中检查一行,就会去tag_post中去检查。
什么时候使用分解联接更好:
可以缓存早期查询的大量数据 , 数据分布在不同的服务器上 , 对于大表使用IN()
替换联接。
查询执行的基础
mysql查询路径:
- 1、客户端将查询发送到服务器;
- 2、服务器检查查询缓存,如果找到了,就从缓存中返回结果,否则进行下一步。
- 3、服务器解析,预处理和优化查询,生成执行计划。
- 4、执行引擎调用存储引擎API执行查询。
- 5、服务器将结果发送回客户端。
step1:客户端将查询发送到服务器
首先需要知道,客户端用一个数据包将查询发送到服务器,一旦客户端发送了查询,剩下的就是等待结果。如果一个查询过大,比如批量插入,有时会出现MySQL server has gone away
的错误,导致的原因可能就是传送的数据太大,导致连接断开了,可以通过SHOW VARIABLES LIKE "max_allowed_packet"
命令查看你的服务器所允许传送的最大数据,可在my.ini
里配置。
服务器发送的响应由许多数据包组成,服务器发送响应的时候客户端必须接收完整的结果集,不能只提取几行数据后要求服务器停止发送剩下的数据。所以,使用LIMIT来获取你所需要的数据行数。
每个MySql连接,或者叫线程,在任意一个给定的时间都有一个状态来标识正在进行的事情。可以使用SHOW [FULL] PROCESSLIST
命令来查看哪些线程正在运行,及其查询状态,Command列显示了状态。
一些常见的状态:
Sleep 线程正在等待客户端,以向它发送一个新语句
Query 线程正在执行查询或往客户端发送数据
Locked 该查询被其它查询锁定
Copying to tmp table on disk 临时结果集合大于tmp_table_size。线程把临时表从存储器内部格式改变为磁盘模式,以节约存储器
Sending data 线程正在为SELECT语句处理行,同时正在向客户端发送数据
Sorting for group 线程正在进行分类,以满足GROUP BY要求
Sorting for order 线程正在进行分类,以满足ORDER BY要求
step2:服务器检查查询缓存
在解析一个查询之前,如果开启了缓存,MySql会检查查询缓存,进行大小写敏感的哈希查找。即使查询和缓存中的查询只有一个字节的差异,也表示不匹配,查询就会进入下一步。
MySql查询缓存保留了查询返回给客户端的完整结果,当缓存命中的时候,服务器马上返回保存的结果(会先检查权限),并跳过解析、优化和执行步骤。查询缓存保留了查询使用过的表,如果表发生了改变(如update),那么缓存的数据就失效了。
step3:服务器解析、优化,生成执行计划
如果查询缓存中没有,下一步就是将查询转变成执行计划,包括解析、预处理和优化的过程。这个过程的任何一步都有可能出现错误,比如语法错误等。这里我们可以看到平时出现的大部分错误是从哪一步抛出来的。
- 1、首先是解析器将查询分解成一个个标识,然后构造一颗“解析树”,解析器保证查询中的标识都是有效的,会检查其中的基本错误,比如字符串上面的引号没有闭合等。
- 2、然后预处理器检查解析器生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。最后,预处理器检查权限。
3、之后,优化器把解析树变成执行计划。一个查询通常可以有很多种执行方式,并且返回同样的结果,优化器的任务就是找到最好的方式。MySql使用的是基于开销的优化器。它会预测不同的执行计划的开销,并且选择开销最小的一个。可以使用
SHOW STATUS LIKE "Last_query_cost"
命令查看查询的开销(但不能作为绝对标准)。如下表名最近一个查询会造成3.4次随机读取。# 查询开销 mysql> show status like 'last_query_cost'; +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | Last_query_cost | 3.418524 | +-----------------+----------+ 1 row in set (0.11 sec)
step4:查询执行引擎执行查询
MySql查询执行引擎使用执行计划来处理查询。和优化部分相反,执行部分不会很复杂。MySql按照执行计划的指令进行查询(执行计划时一个数据结构)。计划中的许多操作都是通过存储引擎提供的方法来完成的。
step4:返回结果到客户端
- 1、执行计划的最后一步是将结果发送到客户端,即使查询没有结果要返回,服务器也会对客户端的联接进行应答,比如有多少行受了影响。
- 2、如果查询是可缓存的,MySql会在这时缓存查询。
- 3、根据MySql的执行机制,一旦它处理了最后一个表并且成功地产生了一行输出,他就会把这个结果发送到客户端。这样的好处是,服务器不用把这一行保存在内存中,二是服务端可以尽快的开始工作。
MySql能处理的一些优化类型
对联接中的表重新排序
MySql优化器中最重要的部分是联接优化器,它决定了多表查询的最佳执行顺序。通常可以用不同的顺序联接几个表,然后得到同样的结果。联接优化器评估不同执行计划的开销,并且选择开销最低的计划。后面再对联接查询细讲。
将外连接转换成内连接
等价变换规则
MySql使用等价变换规则来简化并且规范化表达式。它可以合并或减少一些比较,还可以移除一些恒成立或者恒不成立的判断。例如:5=5 and a >5
将被改写为a>5
,可以看到优化后的结果,1=1是被移除了的。 再比如,(a<b AND a=100) 被转换为 (b>100 AND a=100);条件如果是数字,直接比较数字的效率是最高的。
优化COUNT & MIN & MAX
查找某列最大/最小值,该列又有索引,查找最大值,则会直接找最后一行;最小值,则直接找第一行。因为索引已经排好序了。
COUNT()
对于MyIsam引擎总是保留着表行数的精确值,查找所有行则直接取出,速度很快。但是,如果MIN()/MAX()
查询的列上没有索引,则会进行全表扫描。
预估并转化为常量表达式
a=b and b=1
会被转化为 a=1 and b=1
覆盖索引
当索引中类包含所有查询中需要使用的列的时候,mysql就可以使用索引返回需要的数据,而无需查询对应的数据行。
子查询
提前终止查询
- 一旦满足查询或某个步骤的条件,MySql就会立即停止处理该查询,或者该步骤。比如LIMIT子句,只要满足LIMIT的数目,就会停止查询。
- 再比如,检查到一个不可能的条件,他就会停止整个查询,这个查询在优化阶段就停止了。
列表in的比较
MySql会对IN()
里面的数据进行排序,然后用二分法查找某个值是否在列表中,这个算法的效率是O(Log n)
。等价转换成OR子句的查找效率是O(n)
,在列表很大的时候,OR子句会慢很多。
update与select不能同时进行
MySql不会让你在对同一个表进行UPDATE的同时运行SELECT。
mysql> update emp set post='hello python' where id in (select id from emp where depart_id=3) ;
ERROR 1093 (HY000): You can't specify target table 'emp' for update in FROM clause