最近一次在数据库查询一条数据时,发现查询速度变得巨慢,看了一下查询的where条件也的确是索引字段,因为是基础服务部开发的数据库查询平台,本以为是自己平台出的问题,后来发现其他使用用户并没有出现这样的问题,肯定是SQL本身性能的问题了,于是拿着这个SQL去测试库做了一次执行计划的分析。
测试使用工具:DataGrip
执行这句SQL,我们查看一下执行的时间,执行的时间占用131毫秒,拉取数据占用161毫秒
SELECT * FROM PAYADM.RPMTORD WHERE CRE_DT =20171229;
在Oracle中执行下面的命令,我们可以看一下这句sql的执行过程:
EXPLAIN PLAN FOR SELECT * from PAYADM.RPMTORD WHERE CRE_DT =20171229;
SELECT * FROM TABLE (dbms_xplan.display);
下图就是我们这句命令的执行计划,我们可以看见在表中第二行Operation中出现“TABLE ACCESS FULL”,意思就是我们这句sql使用的是“按全表扫描”,而不是索引检索,接着往下看:1 - filter(TO_NUMBER("CRE_DT")=20171229),到这里真相就大白了,CREDT字段在数据库中实际上一个字符串类型,然而我们where条件中的参数传的是number类型,因此会出现放弃索引,并且每次检索都会做一次TO_NUMBER转换的情况;
然后我们更正sql后再次执行一下,我们发现这个时候执行时间是42毫秒,拉取数据占用161毫秒,执行时间上已经比之前提速了89毫秒,拉取数据花费的时间暂不考虑,因为他和是否有索引无关,同时我们可以看一下此时的执行计划是什么,第二行“Table Access By Index RowID”,此时很明显我们可以看到此次检索使用的是“按索引查找”。
当前我们测试环境下表的数据量将近13万条,而我们实际线上生产环境的数据量已经超过了2000万条,所以全表扫描导致的“慢查询”现象将会变得非常的十分的明显,一个全表扫描可以耗时超过十分钟之上,造成阻塞甚至数据库挂掉。所以我们无论是在开发还是在查数据的时候,所有的带有“WHERE”条件的语句都要在测试环境做一次执行计划,以免出现慢查询的问题。
下面收集了其它博主对SQL优化的总结和一些避免全盘扫描的注意事项:
1.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
2.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描
可以考虑用union
select id from t where num=10 or Name = 'admin'
可以替换为
select id from t where num = 10
union all
select id from t where Name = 'admin'
3. in 和 not in 也要慎用,否则会导致全表扫描,可以用 exists 代替 in
select id from t where num in(1,2,3)
可以替换为
select id from t where num between 1 and 3
4.如果在 where 子句中使用参数,也会导致全表扫描。
select id from t where num = @num (bad !!!)
5.避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
select id from t where num/2 = 100
应该为
select id from t where num = 100*2
6.应尽量避免在where子句中对字段进行函数操作
select id from t where substring(name,1,3) = ’abc’
应改为
select id from t where name like 'abc%'
7.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗
8.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在 处 理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
9.尽可能的使用 varchar/nvarchar 代替 char/nchar,因为首先varchar是一个变长的字段, 变长字段存储空间小,
可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
10.删除JOIN和WHERE子句中的计算字段
SELECT * FROM sales a
JOIN budget b ON ((YEAR(a.sale_date)* 100) + MONTH(a.sale_date)) = b.budget_year_month
应改为
SELECT * FROM PRODUCTSFROM sales a
JOIN budget b ON a.sale_year_month = b.budget_year_month