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