当前位置:首页 > 问答 > 正文

Oracle HINT那些常见的用法,帮你SQL优化时少走弯路

主要综合自Oracle官方文档、Oracle技术社区如OTN和Oracle-Base的常见问题讨论,以及资深DBA的经验分享)

当我们写了一条SQL语句,交给Oracle数据库去执行的时候,数据库内部有一个叫做“优化器”的聪明家伙来决定这条SQL到底该怎么跑最快,它是应该全表扫描呢,还是用索引?如果是多个表连接,先连哪个表后连哪个表?用什么方式连接?这个优化器通常是挺靠谱的,它会根据表的统计数据(比如表有多大、索引的区分度如何)来做出“最优”的选择。

有时候这个优化器也会“犯糊涂”或者信息不全,可能表的统计数据很久没更新了,实际情况已经变了,但优化器不知道;又或者,优化器基于通用规则选出的路径,偏偏不适合你当前这个特殊的业务场景,这时候,我们就需要手动干预一下,给优化器一些“提示”,告诉它:“嘿,伙计,请按我说的这个方法试试看。” 这个“提示”,就是HINT。

HINT的写法很固定,它被放在SQL语句中注释的位置,但必须紧跟着SELECT、UPDATE、DELETE或者INSERT之后,它的标准格式是 /*+ HINT名称(参数) */,注意,开头的斜杠星号后面要紧跟一个加号,这是Oracle识别它是HINT而不是普通注释的关键。

下面就来聊聊那些最常用、最能帮我们解决实际问题的HINT。

全表扫描(FULL)与强制走索引(INDEX)

  • /*+ FULL(表别名或表名) */:这个HINT直接告诉优化器:“对指定的这个表,别管它有没有索引,就直接全表扫描。” 你可能会问,什么时候全表扫描反而更快呢?当你要查询的表本身就很小的時候,比如只有几百行数据,或者你要取出的数据量超过了表总数据量的很大一部分(比如20%-30%以上),这时走索引可能反而需要来回跳读很多数据块,不如直接顺序扫描全表来得干脆,当优化器错误地选择了索引而表实际上很大时,用这个HINT可以纠正它。
  • /*+ INDEX(表别名或表名 索引名) */:这个和FULL正好相反,当你明确知道某个索引非常高效,但优化器不知道为什么(可能是统计数据问题,也可能是SQL写法导致优化器误判)就是不用它时,可以用这个HINT强制它走指定的索引,你有一个针对“状态”字段的索引,而你的查询条件正好是status='ACTIVE',并且活跃的数据只占很少一部分,这时强制走索引效率会高很多。

改变表连接顺序(ORDERED)

Oracle HINT那些常见的用法,帮你SQL优化时少走弯路

  • /*+ ORDERED */:这个HINT告诉优化器:“就严格按照我SQL语句里FROM子句写的表顺序来连接。” 优化器默认会自己决定先访问哪个表(驱动表),后连接哪个表,大部分时候它的选择是对的,但有时我们更了解数据关系,我们知道A表很小,B表很大,并且A表过滤后数据极少,那么先用A表的数据去驱动查询B表会非常高效,如果优化器错误地选择了大表B作为驱动表,就可以用ORDERED HINT来纠正连接顺序。

指定表连接方法

表连接主要有三种方法,针对不同的场景:

  • /*+ USE_NL(表A 表B) */:强制对表A和表B使用嵌套循环连接,这就像是两层循环,外层循环从表A取一行,内层循环就拿着这行的关联字段值去表B里找匹配的行,这种方式在驱动表(表A)很小,并且被驱动表(表B)的连接字段上有高效索引时,速度会非常快。
  • /*+ USE_HASH(表A 表B) */:强制使用哈希连接,它会先读取较小的那个表,在内存中建立一个哈希表,然后读取大表,用连接字段去匹配哈希表,这种方式特别适合两个大表之间的等值连接,当内存充足时效率很高。
  • /*+ USE_MERGE(表A 表B) */:强制使用排序合并连接,它会先把两个表都按照连接字段进行排序,然后像拉链一样合并在一起,这种方式在某些没有索引或者数据已经近乎排序好的情况下可能表现不错。

优化器目标设定(FIRST_ROWS vs ALL_ROWS)

这是一个非常重要的HINT,它决定了优化器是倾向于“快速返回前几行”还是“优化整个结果集的返回时间”。

Oracle HINT那些常见的用法,帮你SQL优化时少走弯路

  • /*+ FIRST_ROWS(N) */:你告诉优化器:“我的用户很着急,希望网页能尽快显示出第一屏数据,哪怕总时间可能会长一点点。” 这时优化器会优先选择那些能最快返回第一批记录的执行路径,比如更倾向于使用索引嵌套循环连接,这在OLTP系统中非常常用。
  • /*+ ALL_ROWS */:你告诉优化器:“我不急,我要的是处理完所有数据的总时间最短。” 这时优化器可能会选择全表扫描、哈希连接等更适合批量处理的方式,这通常在后台跑报表、批处理任务时使用。

并行处理(PARALLEL)

当你的查询要处理海量数据时,比如几亿行,单个人干活太慢怎么办?叫一群人来一起干。

  • /*+ PARALLEL(表别名或表名, 并行度数) */:这个HINT指示Oracle使用多个并行进程来同时扫描和处理这张表。/*+ PARALLEL(t, 8) */ 就是让8个进程一起处理表t,这能极大缩短大数据量查询的响应时间,但要注意,并行处理会消耗更多的CPU和内存资源,不能滥用,一般只在数据仓库类型的系统上对大型表使用。

使用HINT的注意事项和忠告

HINT是一把双刃剑,用得好是神器,用不好反而会带来灾难。

  • 最后的手段:HINT应该是你尝试了所有其他优化方法(比如调整SQL逻辑、更新统计数据、创建合适的索引)之后的最后手段,不要一上来就加HINT。
  • 版本兼容性:不同的Oracle数据库版本,优化器的能力和HINT的支持情况可能不同,一个在10g上好用的HINT,在19c上可能已经过时甚至起反作用。
  • 数据是动态的:今天你加了一个HINT让SQL跑得飞快,但可能下个月数据量暴涨后,这个HINT就成了性能瓶颈的罪魁祸首,HINT剥夺了优化器根据数据变化自我调整的能力。
  • 语法要精确:HINT的语法非常严格,表别名、索引名写错一个字母,HINT就会失效,而且不会报错,只会被静默忽略,这会给排查问题带来很大困扰。

了解这些常见的HINT,就像是给SQL优化工具箱里添置了一套精良的工具,它们能帮助你在优化器“迷路”时指明方向,但切记,它们不是万能的,需要谨慎、有依据地使用。