当前位置: 首页 > 科技观察

教你如何定位和优化SQL语句的性能问题

时间:2023-03-19 10:22:44 科技观察

在当今的软件开发中,关系数据库是最重要的数据存储工具。Oracle和Mysql都需要通过SQL语句与数据库进行交互,我们通常称之为CRUD。在CRUD操作中,最常用的是Read操作。对于不同的表结构,使用不同的SQL语句,性能可能相差很大。本文将基于MySql数据库,介绍如何定位SQL语句的性能问题。定位低性能SQL语句最重要和有效的方法是使用执行计划。执行计划我们知道,无论是何种数据库,何种数据库引擎,在一条SQL语句的执行过程中,都会进行很多相关的优化。对于查询语句,最重要的优化方法就是使用索引。执行计划是显示数据库引擎执行SQL语句的细节,包括是否使用索引,使用什么索引,以及使用索引的相关信息。(https://juejin.im/post/5a52386d51882573443c852a)基本语法explainselect...mysql的explain命令可以用来分析select语句的运行效果。另外explain的扩展扩展可以在原有explain的基础上提供一些额外的查询优化信息,可以通过mysql的showwarnings命令获取。mysql>explainextendedselect*fromaccount;********1.row***************************id:1select_type:SIMPLEtable:accounttype:ALLpossible_keys:NULLkey:NULLkey_len:NULLref:NULLrows:1filtered:100.00Extra:1rowinset,1warning(0.00sec)mysql>showwarnings;**************1.row***************************Level:NoteCode:1003Message:select`dbunit`.`account`.`id`AS`id`,`dbunit`。`account`.`name`AS`name`from`dbunit`.`account`1rowinset(0.00sec)另外,对于分区表的查询,需要使用partitions命令。explainpartitionsselect...执行计划包含的信息不同版本的Mysql、不同存储引擎的执行计划不完全相同,但基本信息是相似的。mysql执行计划主要包含以下信息:(https://juejin.im/post/5a52386d51882573443c852a)id由一组数字组成。表示一个查询中每个子查询的执行顺序;同一个id的执行顺序是从上到下。(https://juejin.im/post/5a52386d51882573443c852a)id不同,id值越大,优先级越高,越早执行。(https://juejin.im/post/5a52386d51882573443c852a)当id为null时,表示一个结果集,不需要查询,经常出现在包括union在内的查询语句中。(https://juejin.im/post/5a52386d51882573443c852a)select_type每个子查询的查询类型,一些常见的查询类型。idselect_type描述1SIMPLE不包含任何子查询或联合查询2PRIMARY包含子查询,最外层查询显示为PRIMARY3SUBQUERY是包含在select或where子句中的查询4??DERIVEDfrom子句中包含的查询5UNION出现在在union之后的查询语句中,6UNIONRESULT从UNION中获取结果集,如上面第三个例子中表查询涉及到的数据表。如果查询使用别名,则别名会显示在此处。如果不涉及数据表的操作,则显示为null。如果显示为尖括号括起来的,说明这是一个临时表,后面的N在执行计划中。id,表示结果来自本次查询。如果用尖括号括起来,则类似于也是临时表,表示结果来自联合查询id为M和N的结果集。typeaccesstypeALLScanfulltabledataindextraverseindexrangeindexrangesearchindex_subqueryuserefunique_subqueryinsubqueryuseeq_refref_or_nullinsubqueryoptimizedrefforNullindexfulltextusefull-textindexrefusenon-***indexsearch数据eq_ref是与连接查询中的PRIMARYKEY或UNIQUENOTNULL索引关联。const使用主键或***索引,匹配结果只有一条记录。systemconst连接类型的特例,要查询的表是系统表。(https://juejin.im/post/5a52386d51882573443c852a)性能从好到坏的顺序是:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,exceptALL,所有其他类型都可以使用索引,除了index_merge,其他类型只能使用一个索引。因此,如果通过执行计划发现某个表的查询语句的类型显示为ALL,则应考虑增加索引,或者改变查询方式,使用索引进行查询。possible_keys可能用到的index,注意可能用不到。如果查询涉及的字段有索引,则列出索引。当列为NULL时,需要考虑当前SQL是否需要优化。key表示MySQL在查询中实际使用的索引,如果没有使用索引,则显示为NULL。TIPS:如果在查询中使用了覆盖索引(覆盖索引:被索引的数据覆盖了所有要查询的数据),该索引只出现在key列表中。当select_type为index_merge时,这里可能出现两个以上的索引,其他select_type可能只出现一个索引。key_length索引长度char(),varchar()索引长度计算公式:(CharacterSet:utf8mb4=4,utf8=3,gbk=2,latin1=1)*列长+1(允许空)+2(变长列)其他类型索引长度计算公式:ex:CREATETABLE`student`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`name`varchar(128)NOTNULLDEFAULT'',`age`int(11),PRIMARYKEY(`id`),UNIQUEKEY`idx`(`name`),KEY`idx_age`(`age`))ENGINE=InnoDBAUTO_INCREMENT=2DEFAULTCHARSET=utf8mb4;name索引长度为:编码为utf8mb4,列长为128,NULL则不是允许,字段类型为varchar(128)。key_length=128*4+0+2=514;(https://juejin.im/post/5a52386d51882573443c852a)age索引长度:int类型占4位,允许为null,索引长度为5。(https://juejin.im/post/5a52386d51882573443c852a)ref表示上述表的连接匹配条件,即使用哪些列或常量来查找索引列上的值。如果是常量等价查询,这里会显示const。如果是连接查询,驱动表的执行计划会显示驱动表的关联字段。如果条件使用表达式或函数,或者条件列有内部隐式转换,它可能会显示funcrows返回的估计结果集数,注意这不是精确值。extraextra的信息非常丰富,常见的有:使用索引使用覆盖索引使用where使用where子句过滤结果集使用filesort使用文件排序,发生在使用非索引列进行排序时,消耗很大性能,并应尽可能优化。Usingtemporary使用临时表。SQL的一些优化建议1、不要写太复杂的SQL语句。SQL语句应尽可能简单,不要嵌套太多层。2.使用“临时表”缓存中间结果。简化SQL语句的一个重要方法是使用临时表来临时存储中间结果,这样可以避免程序中多次扫描主表,大大减少阻塞,提高并发性能。3、使用like时注意是否会导致全表扫描。有时需要执行一些模糊查询,比如selectidfromtablewhereusernamelike'%hollis%'keyword%hollis%。由于hollis前面用了“%”,所以这个查询会用到全表扫描,除非必要,不要在关键字前加%。4.尽量避免使用!=或<>运算符。如果在where语句中使用!=或<>,引擎将放弃使用索引并执行全表扫描。5.尽量避免使用或连接条件。如果在where子句中使用or来连接条件,引擎会放弃使用索引转而进行全表扫描。可以用selectidfromtwherenum=10unionallselectidfromtwherenum=20代替selectidfromtwherenum=10ornum=206,尽量避免在where子句中使用in和notin,引擎会放弃使用索引而进行全表扫描。可以使用selectidfromtwherenumbetween10and20代替selectidfromtwherenumin(10,20)7.可以考虑强制查询使用索引select*fromtableforceindex(PRI)limit2;(强制使用主键)select*fromtableforceindex(hollis_index)limit2;(强制使用索引"hollis_index")select*fromtableforceindex(PRI,hollis_index)limit2;(强制使用索引“PRIandhollis_index”)8、尽量避免使用表达式、函数等操作作为查询条件9、尽量避免大事务操作,提高系统并发性。10、尽量避免使用游标11、不要在任何地方使用select*fromt,将“*”替换为特定的字段列表,不返回任何未使用的字段。12、尽量使用varchar/nvarchar,而不是char/nchar。13.尽可能使用数字字段。尽量不要将只包含数值信息的字段设计为字符类型,这样会降低查询和连接性能,增加存储开销。14、索引越多越好,索引可以提高相应select的效率,但是也会降低insert和update的效率。15.并不是所有的索引都对查询有效,SQL是根据表中的数据进行查询优化的,当索引列有大量重复数据时,SQL查询可能不会使用索引