为了获(huò)得稳定(dìng)的执行性能,SQL语句越简(jiǎn)单(dān)越好。对复杂的SQL语句,要设法对之进行简化(huà),本文给大家介绍优化SQL语句提高(gāo)数据库性能。
现在(zài)数据越(yuè)来越(yuè)复杂和庞(páng)大,很多时候影响程序运行性能不(bú)理想的原因中(zhōng)除(chú)了一部分是因为应用程(chéng)序的负载确实超过了(le)服务器的实际处理能力(lì)外,更多的是因(yīn)为系统存在大量的SQL语(yǔ)句(jù)需要优(yōu)化(huà)。
一、问题的提出
在项目实际使用中,数据是一个(gè)长期累计的过程,随着数据(jù)库中(zhōng)数据的增(zēng)加,系统的响应速度就成为目前系(xì)统需要解(jiě)决的最主要的问题之一。系统优化中一(yī)个很重要的方面就(jiù)是SQL语句的优化。对于海量数据(jù),劣质SQL语句和优质(zhì)SQL语句之间的速度(dù)差别可以达到(dào)成(chéng)千(qiān)上百倍,因此高质量的SQL语(yǔ)句,更能提高系统的可用性(xìng)。
二、SQL语(yǔ)句编写注意问题(tí)
下面就某些SQL语句(jù)的where子句编写中(zhōng)需(xū)要注(zhù)意的问(wèn)题作详细介绍。在这些(xiē)where子句(jù)中,即使(shǐ)某些(xiē)列存在索引(yǐn),但(dàn)是由于(yú)编写了(le)劣质的SQL,系统在(zài)运行该SQL语句时(shí)也不能使(shǐ)用该索引,而同样使用全表扫描,这就造成了响应速度的(de)极(jí)大降低(dī)。
1. 操作符优化
(a) IN 操作符(fú)
在使用中尽量用EXISTS替(tì)代IN、用(yòng)NOT EXISTS替(tì)代NOT IN 。
在(zài)许多基于(yú)基础表的查询中,为了(le)满(mǎn)足一个条件,往往需要对另一个表进行联接。在这种情况(kuàng)下, 使用EXISTS(或NOT EXISTS)通(tōng)常(cháng)将提高查询的效率。。在子查询中,NOT IN子句将执行一个内部的排序(xù)和(hé)合并。 无论在哪种(zhǒng)情况下,NOT IN都是(shì)最低效(xiào)的 (因为它对子(zǐ)查询中的表执(zhí)行了一个全(quán)表(biǎo)遍(biàn)历(lì))。。为了避免(miǎn)使用NOT IN ,我们(men)可以把它(tā)改写(xiě)成外(wài)连接(jiē)(Outer Joins)或NOT EXISTS。
例子:
(推荐(jiàn))select* from dt_article where exists(select id from dt_article_category wheredt_article_category。id=dt_article。category_id andtitle='公司新闻(wén)')
(不推荐)select* from dt_article where category_id in (select id from dt_article_categorywhere title='公(gōng)司新闻')
(b) IS NULL 或IS NOT NULL操作(判断字(zì)段是否为空)
判断字段是否为空(kōng)一(yī)般是不(bú)会应(yīng)用索引的,因为索引(yǐn)是不索引空(kōng)值(zhí)的。不能用null作索引,任何(hé)包含null值的列(liè)都将不(bú)会(huì)被包含(hán)在索引(yǐn)中。即使索(suǒ)引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排(pái)除。也(yě)就(jiù)是说如果某列存(cún)在空值,即使对该列建索引也不会提高性能。任何在where子句中使用is null或is not null的(de)语句优(yōu)化器是不允许使用(yòng)索引的。
例(lì)子:
(推荐)select* from dt_article where title>'';
(不推荐)select* from dt_article where title is null;
(c) > 及 < 操作符(大于或小(xiǎo)于操作符)
(推荐)select * from dt_article where id>=101;
(不推荐)select * from dt_article where id>100;
两者的区(qū)别在(zài)于, 前者将直(zhí)接跳到(dào)第(dì)一(yī)个id等于101的记录而(ér)后者(zhě)将(jiāng)首先定位到id=100的记录并且向前扫(sǎo)描到(dào)第一个id大于100的记(jì)录(lù)。
(d)LIKE操作符
LIKE操(cāo)作符可以应(yīng)用通配符查询,里面(miàn)的通配符组合可(kě)能达到几乎是任意的查询,但是如果用得不好则会产生性能上的问题(tí),如like '%福(fú)瑞希%'这种查询不会引(yǐn)用索引(yǐn),而like'福瑞希%'则(zé)会(huì)引用范围索(suǒ)引。
一个实(shí)际例子:用(yòng)dt_article表中内容可来查询, content like'%福瑞希%'这个条件会产生全(quán)表扫描,如果改成contentlike '福瑞(ruì)希%'则(zé)会利用content的索引进行范围(wéi)的查询,性能(néng)肯定大大(dà)提高。
在很多情(qíng)况(kuàng)下可能无法避免这种情况(kuàng),但是一定要心中有底(dǐ),通配符如此使用会(huì)降低(dī)查询(xún)速度。然而当通配符出现(xiàn)在字(zì)符串其他位置时,优化(huà)器就能利用索引。
(e) UNION操作符
当SQL语句需要UNION两个(gè)查询结果(guǒ)集(jí)合时,这(zhè)两(liǎng)个结(jié)果集合会以(yǐ)UNION-ALL的方式被合并, 然后在输(shū)出(chū)最(zuì)终结果前进行(háng)去重和排序。 假如用UNION ALL替代UNION, 这样(yàng)排序就不是必要了。 效率就会因此得到提高。 需要注重的是(shì),UNION ALL 将重(chóng)复输出两个(gè)结果集合中相同记录。 因此各位还是要从(cóng)业务需求分析使(shǐ)用(yòng)UNIONALL的可行性。 UNION 将对结果集合去重排序,这个(gè)操作会使(shǐ)用到(dào)SORT_AREA_SIZE这块(kuài)内存。 对于(yú)这块内(nèi)存的优化(huà)也是(shì)相当重要的。
(f) NOT
我们(men)要避(bì)免在索引(yǐn)列(liè)上使用NOT, NOT会产生(shēng)在和在索引列上使用函(hán)数相同的影响。 当查询列碰到”NOT,他就会停(tíng)止使用索引转而执行全表(biǎo)扫描。
(g) OR
通常情况下, 用UNION替换WHERE子句中(zhōng)的OR将会(huì)起到较好的效果。 对索引列(liè)使用OR将(jiāng)造成全表扫描(miáo)。 注重(chóng), 以上规则只针(zhēn)对多个索引列有效。 假如有column没(méi)有被索(suǒ)引, 查询效率(lǜ)可能会因为(wéi)你(nǐ)没有选(xuǎn)择OR而(ér)降低。 在下面(miàn)的例(lì)子中, title和(hé)category_id上都建有(yǒu)索引。
(推荐)select * from dt_article where title='清洗(xǐ)空(kōng)气' union all select * from dt_article where category_id=92
(不推荐)select * from dt_article where title='清洗空气(qì)' or category_id=92 假如你坚持要用OR, 那就需(xū)要返(fǎn)回记录最少(shǎo)的索引列写在最前面。
另外(wài)在一些情况下,也可以使用IN来替代OR, 这(zhè)是一条简单易(yì)记(jì)的规则,但(dàn)是实际的执行(háng)效果还须检验。
(推荐)select * from dt_article where category_id in (89,92)
(不推荐)select * from dt_article where category_id=92 or category_id=89
(h) DISTINCT
当提交一个包含一对多(duō)表信息的(de)查询时,避免在SELECT子(zǐ)句中使用DISTINCT。 一般可以考虑用EXIST替换, EXISTS 使查询更(gèng)为迅速,因为RDBMS核(hé)心模块将在子查询的条件一(yī)旦满(mǎn)足后,马上返回结果。
2. SQL书(shū)写的(de)影响
(a) WHERE后面的条(tiáo)件顺序影响(xiǎng)
WHERE子句(jù)后(hòu)面的(de)条件顺序对大数据量表的(de)查询会产生直接(jiē)的影响。如:
select * from dt_article where category_id=92 and is_hot=1
select * from dt_article where is_hot=1 and category_id=92
以上两个SQL中(zhōng)category_id(电压等级)及is_hot(销户(hù)标志)两个(gè)字段都(dōu)没(méi)进行索引(yǐn),所以执行(háng)的时(shí)候都是全表扫描,第(dì)一条SQL的is_hot=1在记录集内比率(lǜ)为(wéi)99%,而category_id=92的比率只为1%,在进行第一条SQL的时候99%条记录都进(jìn)行category_id及is_hot的比(bǐ)较,而(ér)在进(jìn)行第二(èr)条SQL的时候1%条记录(lù)都进行category_id及is_hot的比较,以此可以得出第二条(tiáo)SQL的CPU占用率明显比第(dì)一条低(dī)。
WHERE解析(xī)是采用自下而上的顺(shùn)序解析(xī)WHERE子句,根据这(zhè)个(gè)原理,表之间的连接必须(xū)写在其他WHERE条(tiáo)件之前, 那些可以(yǐ)过滤(lǜ)掉最大(dà)数量记(jì)录的条件(jiàn)必须(xū)写(xiě)在WHERE子(zǐ)句的末尾。
3. 更多(duō)方(fāng)面SQL优化资料分(fèn)享(xiǎng)
(1) 选择最有效率的(de)表(biǎo)名顺序(只在基于规则的优化器中(zhōng)有效):
ORACLE 的解析器(qì)按照从右到左的顺序处理FROM子(zǐ)句中的表名,FROM子句中写在(zài)最(zuì)后的表(基础表 driving table)将(jiāng)被最先处理,在FROM子句中包含多个表的情况下(xià),你(nǐ)必须选择记录条数最少的表作为基础表。如(rú)果(guǒ)有(yǒu)3个以上的表连接(jiē)查询(xún), 那就需要选择交叉表(intersectiontable)作为(wéi)基础表, 交叉表(biǎo)是指(zhǐ)那个被其他(tā)表所引用的表.
(2) SELECT子句中避免使用 ‘ * ‘:
ORACLE在(zài)解析(xī)的过程中, 会(huì)将'*' 依次(cì)转(zhuǎn)换成所有(yǒu)的(de)列名, 这个工(gōng)作是通过查询数据字典完成的, 这意味着将耗费更多的时间。
(3) 减少访(fǎng)问数据库的次数:
ORACLE在(zài)内部执行了许多工作: 解(jiě)析SQL语句, 估算索引的利用率, 绑定变量 , 读数据(jù)块等。
(4) 整合简单(dān),无关(guān)联的数据库(kù)访问(wèn):
如果你有几(jǐ)个(gè)简单的数据(jù)库查(chá)询语句,你可以把它(tā)们整合到一个查询中(zhōng)(即使它们之间没有关系) 。
(5) 用(yòng)TRUNCATE替代DELETE:
当(dāng)删除表中的记录时,在(zài)通常情况下(xià), 回滚(gǔn)段(rollbacksegments ) 用来(lái)存放(fàng)可以被恢复(fù)的信息(xī). 如果你没有COMMIT事务,ORACLE会将数据(jù)恢复到(dào)删除之前的状态(tài)(准确地(dì)说是恢复到执行删除命令之前的状况) 而当运用TRUNCATE时, 回滚段不再存放任何可被恢(huī)复的信息.当命令运(yùn)行后(hòu),数据不能被(bèi)恢复.因此很(hěn)少的资源被调用,执(zhí)行时间也会很短. (译者按: TRUNCATE只在删除全(quán)表适用,TRUNCATE是DDL不是(shì)DML) 。
(6) 尽量(liàng)多使用(yòng)COMMIT:
只要有可能,在(zài)程序中尽量多使(shǐ)用(yòng)COMMIT, 这样程序的性(xìng)能得到提高,需(xū)求也会因(yīn)为COMMIT所释放的(de)资源而(ér)减少,COMMIT所释放的资(zī)源:
a. 回(huí)滚(gǔn)段(duàn)上用于恢(huī)复数据的信息.
b. 被程(chéng)序语句获(huò)得(dé)的锁
c. redo log buffer 中的空间
(7) 通过(guò)内部函数提(tí)高SQL效(xiào)率(lǜ):
复杂的SQL往往牺牲了执行效率. 能够掌(zhǎng)握上(shàng)面的运用函数解决问题的方法(fǎ)在实际工作中是(shì)非常有意义的。
(8) 使(shǐ)用表的(de)别名(Alias):
当在SQL语句中连(lián)接(jiē)多个表时, 请使用(yòng)表(biǎo)的别(bié)名并把别名前缀于每个(gè)Column上(shàng).这样一来,就(jiù)可以减少解(jiě)析的(de)时间并减少那些由Column歧义引起(qǐ)的语法错(cuò)误。
(9) 总是使用索引(yǐn)的(de)第一个(gè)列:
如(rú)果索引是(shì)建立在多个列上, 只有在它(tā)的第一个列(leading column)被where子句引用时(shí),优化器(qì)才会选择使(shǐ)用该索引. 这(zhè)也是(shì)一条简单而(ér)重要的规则(zé),当仅(jǐn)引用(yòng)索引的第二个(gè)列时,优化器使(shǐ)用了全表扫描而忽略了索引。
(10) 避免使用耗(hào)费资源的操作:
带(dài)有(yǒu)DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句(jù)会启动SQL引(yǐn)擎(qíng)执(zhí)行耗费资(zī)源的排序(xù)(SORT)功(gōng)能. DISTINCT需(xū)要一次排(pái)序操作, 而其他的至少需要(yào)执行两次排序. 通常, 带有UNION, MINUS , INTERSECT的(de)SQL语句都可以用其他方式重写. 如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以(yǐ)考虑的, 毕竟它(tā)们(men)的可读性很(hěn)强。