谈谈MySQL查询优化
Posted Z-hhhhh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谈谈MySQL查询优化相关的知识,希望对你有一定的参考价值。
1、硬件优化
一般来讲,硬件的优化是成本最高效果最差的,服务器性能对数据库的读写能力的影响尤其体现在磁盘I/O上。mysql数据库频繁的CURD操作是十分吃磁盘I/O的,一般可以认为磁盘I/O是硬件方面制约mysql数据库性能的最大因素。
解决方法:目前一般使用RAID0-1磁盘阵列来解决。
2、储存引擎
在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。
MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB用于事务处理应用程序,具有众多特性,包括ACID事务支持。如果应用中需要执行大量的INSERT或UPDATE操作,则应该使用InnoDB,这样可以提高多用户并发操作的性能。在百万级数据及更大数据情况下,mysql InnoDB 的索引表现更加优秀! 它的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。
3、通信协议
MySQL客户端/服务端通信协议是“半双工”的:在任一时刻,要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,这两个动作不能同时发生。一旦一端开始发送消息,另一端要接收完整个消息才能响应它,所以我们无法也无须将一个消息切成小块独立发送,也没有办法进行流量控制。当查询语句很长的时候,需要设置max_allowed_packet参数。尽量保持查询简单且只返回必需的数据,减小通信间数据包的大小和数量是一个非常好的习惯,这也是查询中尽量避免使用SELECT *以及加上LIMIT限制的原因之一。
4、查询缓存
(mysql 8.0 版本开始弃用查询缓存)相同的查询语句和查询结果会以键值对的方式缓存在内存中,MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存(完全一样),MySQL会立刻返回结果,跳过了解析、优化和执行截断。任何字符上的不同,例如空格、注释等都会导致缓存的不命中。可以通过SQL_CACHE和SQL_NO_CACHE来控制某个查询语句是否需要进行缓存。命中率低,8.0版本以后已经弃用。
5、表结构优化
5.1、分页、分表&分库
(1)分页。使用Limit查询。但是偏移量越大越缓慢。
(2)垂直/水平分表、分库
对于字段比较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。越小的列查询得越快。
5.2、字段
(1)不使用null(有一定争议),因为null会占据额外的空间;
(2)如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。其中一个缺点是枚举的字符串列表是固定的,添加和删除字符串(枚举选项)必须使用ALTER TABLE(如果只只是在列表末尾追加元素,不需要重建表)。
(3)固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。所以,按照查询速度: char最快, varchar次之,text最慢。
5.3、增加中间表
对于需要经常联合查询的表,可以建立中间表以提高查询效率。通过建立中间表,把需要经常联合查询的数据插入到中间表中,然后将原来的联合查询改为对中间表的查询,以此来提高查询效率。
5.4、添加索引
(1)索引的建立遵循最左前缀匹配原则。
(2)尽量考虑在where或order 不要后涉及的列上建立索引。
(3)建立索引的列必须是一个单独、独立的列,完全“干净”的列。不能存在任何运算。
(4)多列索引
①当出现多个索引做相交操作时(多个AND条件),通常来说一个包含所有相关列的索引要优于多个独立索引;
②当出现多个索引做联合操作时(多个OR条件),对结果集的合并、排序等操作需要耗费大量的CPU和内存资源,特别是当其中的某些索引的选择性不高,需要返回合并大量数据时,查询成本更高。所以这种情况下还不如走全表扫描。
(5)覆盖索引。如果一个索引包含或者说覆盖所有需要查询的字段的值,那么就没有必要再回表查询。
5.5、优化Union
除非确实需要服务器去重,否则就一定要使用UNION ALL,如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用ALL关键字,MySQL总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。
6、基于查询语句规则的优化
6.1、尽量不使用select * from
table。从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。
6.2、条件化简
①移除不必要的括号
②常量传递
③等值传递
④移除没用的条件
⑤表达式计算。
⑥Having和where合并,having 移到where后面。
⑦常量表检测:查询的表中一条记录没有,或者只有一条记录。使用主键等值匹配或者唯一二级索引列等值匹配作为搜索条件来查询某个表。
6.3、使用limit
①分页查询(但是要考虑偏移量不能太大)
②如果只需要一行数据,使用limit 1
6.4、外连接消除
内连接可能通过优化表的连接顺序来降低整体的查询成本,而外连接却无法优化表的连接顺序
6.5、子查询优化
行子查询时, MYSQL需要为内层查询语句的查询结果建立一个临时表。然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表。因此,子查询的速度会受到一定的影响。如果查询的数据量比较大,这种影响就会随之增大在 MYSQL中,可以使用连接(JOIN)查询来替代子查询。连接查询不需要建立临时表,其速度比子查询要快,如果查询中使用索引的话,性能会更好。连接之所以更有效率,是因为 MYSQL不需要在内存中创建临时表来完成查询工作。
6.6、大表ALTER
TABLE非常耗时,MySQL执行大部分修改表结果操作的方法是用新的结构创建一个张空表,从旧表中查出所有的数据插入新表,然后再删除旧表。尤其当内存不足而表又很大,而且还有很大索引的情况下,耗时更久。
7、使用explain
一条查询语句在经过MySQL查询优化器的各种基于成本和规则的优化会后生成一个所谓的执行计划,这个执行计划展示了接下来具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等。Explain会展示执行计划的具体信息。
比如计算执行计划是否最优会考虑IO成本,CPU成本,内存开销,总时长时间等,但不一定是我们业务场景下要求的最优的执行计划。因此需要借助这个执行计划的分析,去判断应该怎样选择执行计划,改进自己的查询语句以使查询执行起来更高效。
8、缓冲池
磁盘读取文件太慢,跟不上CPU读取速度,因此InnoDB用内存作为缓存。设置缓冲池Buffer Pool。磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。
Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,存放一些页。InnoDB使用了许多链表来管理Buffer Pool。
LRU链表分为young和old两个区域,可以通过innodb_old_blocks_pct来调节old区域所占的比例。首次从磁盘上加载到Buffer Pool的页会被放到old区域的头部,在innodb_old_blocks_time间隔时间内访问该页不会把它移动到young区域头部。在Buffer Pool没有可用的空闲缓存页时,会首先淘汰掉old区域的一些页。
我们可以通过指定innodb_buffer_pool_instances来控制Buffer Pool实例的个数,每个Buffer Pool实例中都有各自独立的链表,互不干扰。
可以通过innodb_buffer_pool_size来调整它的大小。
以上是关于谈谈MySQL查询优化的主要内容,如果未能解决你的问题,请参考以下文章
Redis技术专区「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南