mysql(11)、数据查询优化

优化数据访问

查询性能低下的最基本原因就是访问了太多数据,一些查询要不可避免地筛选大量的数据,大部分性能欠佳的查询都可以用减少数据访问的方式进行优化。

查询不需要的数据

首先分析应用程序是否正在获取超过需要的数据,这通常表现在获取了过多的行或列。一些查询先向服务器请求不需要的数据,再丢掉他们,这个让服务器造成了额外的负担,增加了网络开销,消耗了内存和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

猜你喜欢

转载自www.cnblogs.com/fqh202/p/9428040.html