实际工作中,我们每个人难免都会要写SQL,执行SQL,但是有时时候执行非常慢,甚至获得不了结果。这时候你会怎么办?放弃?去苦口婆心的求隔壁房间胡子擦擦的猥琐DBA大叔?
NO,正确方法是先检查一下你的SQL语句。本文虫虫给你列出来用来排查SQL查询比较慢的常见方法和对策。文中所有方法和例子均基于PostgreSQL,当然这些都可以快速移植到MySql和其他数据库,因为SQL语句基本上都是相通的。
了解现状
首先,需要先清楚当前数据的环境情况。数据库是不是很繁忙?有多少用户在线,多少查询在执行?当时失败正处在高峰期?
对策:
可以通过询问数据库来了解数据库当前状态。不需要你去@ DBA或者运维,你只需执行SQL语句就可以获得这些信息:
我们可以通过以下语句列出当前所有运行的和空闲的查询:
select * from pg_stat_activity
下面的语句查找导致锁表的查询:
select pid,usename,pg_blocking_pids(pid) as blocked_by,query as blocked_query from pg_stat_activity where cardinality(pg_blocking_pids(pid))> 0;
锁
表当时正在更新吗?如果你查询时候恰好遇到ETL进程在更新被锁定的表,你也就无法对其查询。
对策:
了解这些ETL更新执行时间,避开这些时间再执行查询。
有针对性的查询
知道了当前数据库的状态。现在可以具体从你的SQL语句入手了。首先看你的SQL语句:SELECT * from XXX
咦,为啥要 SELECT * ?
对策:
如果知识为了了解表的结构,请从模式树获取表字段。
d 表名
为了执行更快,只SELECT具体的字段值,不要用SELECT * ;
如果有一个特别大的表或宽表(表示字段很多),查询引擎不可能将所有数据都取过来。使用'LIMIT'来限制查询,如果你确实需要关注每一行的内容那另说;
如果要COUNT计算,不要运行查询通过查询结果底部统计行数来获取统计,请使用计算行数的子查询:
select count(*) from(selectidfrom userswhere preferred_language = 'zh_CN'and private_profile = True) as temp;
大小写
PostgreSQL是区分大小写的,这对于Windows下用户习惯SQL Serve的人来说可能有点别扭。
对策:
如果"小写化"或"大写化"数据,比较费劲,在将数据加入查询中之前,先查看字段的形式。
如果在join时候需求,请仅在join一侧使用;尝试使用ILIKE进行不区分大小写的匹配。
避免使用NOT IN
尽量避免使用"IN"或"NOT IN"。此操作需要全表扫描,查询引擎需要对比每一行数据以检查是否满足条件。
对策:
尝试使用"EXCEPT"或"NOT EXISTS",这些对查询计划的影响远小于"NOT IN"。
CTE
CTE(公共表达式)比子查询更易于阅读,但在PostgreSQL中该角色优化有限,查询优化器无法对其变动约束条件实现查询优化。
对策:
CTE和子查询虽然都很有用,但是都有其适用范围。使用CTE时候请考虑表大小,可能返回的行数以及写入时在CTE中执行的操作。
通配符和模糊查询
在LIKE的开头和结尾使用通配符会降低查询效率。并且可能会获得比预期更多的结果。
对策:
在必需地方使用通配符,通常简易,只在LIKE后的开头或者结尾一端使用:
select name, email,location from users where name like 'CC%';
尝试写入一张表
将几个嵌套查询用作函数进行操作非常昂贵,这时候尝试写入表会更快。
对策:
如果流程有很多步骤,请考虑创建临时表,以便加入较小的数据子集。
视图的视图
视图是引用查询运行的查询结果。如果要调用多个视图,或者更复杂情况下访问视图的视图,要求查询引擎运行多个查询返回结果。
对策:
如果需要每天/每周/每月等定期的查询快照,不是动态过滤的查询视图,请使用定期结果入表,而不要用视图。
如果要使用嵌套视图,请考虑是否有更直接的方法通过编写查询来获取所需的结果,不要使用多个查询的嵌套视图。
索引
索引通过对数据字段序列化来加速查询,可以以让数据库引擎快速定位数据的位置。索引类型决定了索引的工作方式。
对策:
对数据表中需要经常查询的,使用频繁的字段(或者字段组合)加索引。
评估表中现存的索引确保表中没有太多的索引或者有无用的索引。
总结
本文列出了SQL查询中常见可能会导致性能问题事项,并提供具体对策用以优化。当然这些只是给出了一般性质的建议,针对具体问题具体分析才是解决问题的关键。