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