接上篇文章和大家继续聊一下布隆过滤器(bloom Filter)在ORACLE数据库中的应用。
ORACLE数据库在以下的场景中使用到了布隆过滤器:
减少并行联接中从进程之间的数据通信量。 ※1
实现连接过滤器修剪。 ※2
支持结果缓存。 ※3
※1:available from Oracle Database 10g Release 2
※2:available from Oracle Database 11g Release 1
※3:available from Oracle Database 11g Release 1,但是因为没有具体资料,没有办法进行详细叙述。如果哪位读者有详细一点的资料,欢迎补充。
关于并行联接
当两个表采用并行执行哈希(hash)或合并联接(merge join)时,需要在有多个从属进程之间交换数据集。例如,在以下查询的执行计划中,使用三组从属进程(每组从属进程拥有列 TQ 中的不同标识值)。
SELECT * FROM t1, t2 WHERE t1.id = t2.id AND t1.mod = 42
-------------------------------------------------------------------------
| Id | Operation | Name | TQ |IN-OUT| PQ Distrib |
-------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | |
| 1 | PX COORDINATOR | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | Q1,02 | P->S | QC (RAND) |
|* 3 | HASH JOIN BUFFERED | | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10000 | Q1,00 | P->P | HASH |
| 6 | PX BLOCK ITERATOR | | Q1,00 | PCWC | |
|* 7 | TABLE ACCESS FULL| T1 | Q1,00 | PCWP | |
| 8 | PX RECEIVE | | Q1,02 | PCWP | |
| 9 | PX SEND HASH | :TQ10001 | Q1,01 | P->P | HASH |
| 10 | PX BLOCK ITERATOR | | Q1,01 | PCWC | |
| 11 | TABLE ACCESS FULL| T2 | Q1,01 | PCWP | |
-------------------------------------------------------------------------
3 - access("T1"."ID"="T2"."ID")
7 - filter("T1"."MOD"=42)
下面简单地说明这个执行计划:
Id 5-7:
从属进程(Q1,00)应用过滤器“7 - filter("T1"."MOD"=42)”扫描表 t1 ,然后将过滤后的数据发送到从属进程(Q1,02)。
Id 9-11:
从属进程(Q1,01)扫描表 t2 ,将数据发送到集从属进程(Q1,02)。
Id 4,8:
从属进程(Q1,02)接收从属进程(Q1,00),(Q1,01)的数据。
Id 3:
从属进程(Q1,02)把接收的数据生成哈希表。
Id 2:
从属进程(Q1,02)把哈希表联接的行发送到主进程。
Id 0-1:
主进程接收数据并返回结果集。
下图说明了三组从进程之间的通信。
需要注意的是,联接由从属进程(Q1,02)执行。因此,从属进程(Q1,00) 和 从属进程(Q1,01) 发送的部分数据可能会因为不满足联接条件而被丢弃。换句话说,可能有不必要数据被发送到从属进程(Q1,02)而造成通信开销特别明显。这种情况下,如果使用布隆过滤器在传递给从属进程(Q1,02)之前过滤掉不满足联接条件的大多数数据,就能避免不必要的通信,从而提高效率。
下面我们来看看下面的执行计划。
--------------------------------------------------------------------------
| Id | Operation | Name | TQ |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | |
| 1 | PX COORDINATOR | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | Q1,02 | P->S | QC (RAND) |
|* 3 | HASH JOIN BUFFERED | | Q1,02 | PCWP | |
| 4 | PX JOIN FILTER CREATE| :BF0000 | Q1,02 | PCWP | |
| 5 | PX RECEIVE | | Q1,02 | PCWP | |
| 6 | PX SEND HASH | :TQ10000 | Q1,00 | P->P | HASH |
| 7 | PX BLOCK ITERATOR | | Q1,00 | PCWC | |
|* 8 | TABLE ACCESS FULL| T1 | Q1,00 | PCWP | |
| 9 | PX RECEIVE | | Q1,02 | PCWP | |
| 10 | PX SEND HASH | :TQ10001 | Q1,01 | P->P | HASH |
| 11 | PX JOIN FILTER USE | :BF0000 | Q1,01 | PCWP | |
| 12 | PX BLOCK ITERATOR | | Q1,01 | PCWC | |
| 13 | TABLE ACCESS FULL| T2 | Q1,01 | PCWP | |
--------------------------------------------------------------------------
3 - access("T1"."ID"="T2"."ID")
8 - filter("T1"."MOD"=42)
与前面的执行计划相比,多了两个附加操作(Id 4 和 11)。
•Id 4,从属进程(Q1,02) 创建了一个布隆过滤器:BF00000(如果有多个布隆过滤器,则增加数值,即第二个过滤器将命名为 :BF0001)。
•Id 11,从属进程(Q1,01) 将数据发送到布隆过滤器:BF00000。用以过滤掉不满足联接条件的数据(false positives除外)。
查询优化器(CBO)会根据估计的联接选择性和要处理的数据量决定是否使用布隆过滤器。也可以使用Hint px_join_filter或no_px_join_filter来开启或禁止布隆过滤器。但是,如果数据量较小,即使指定px_join_filter,也无法强制查询优化器使用布隆过滤器。若要完全禁用此功能,可以把初始化参数_bloom_filter_enabled设置为 FALSE。
如果要显示布隆过滤器的有关信息,可以使用动态性能视图 v$sql_join_filter。
例如,以下查询可以检查布隆过滤器筛选和探测的行数。
SQL> SELECT filtered, probed, probed-filtered AS sent
2 FROM v$sql_join_filter
3 WHERE qc_session_id = sys_context('userenv','sid');
FILTERED PROBED SENT
---------- ---------- ----------
81795 100000 18205
请注意,直到 Oracle 10.2.0.3,动态性能视图 v$sql_join_filter 仅返回活动的布隆过滤器的信息。另一个动态性能视图,可以显示由于布隆过滤器减少的通信是 v$pq_tqstat。
连接过滤器修剪
查询优化器(CBO)能够根据Where条件执行分区修剪。一直到 Oracle Database 10g Release 2,当分区修剪基于联接条件时,查询优化器可以使用哈希和合并联接进行分区修剪。这种类型的修剪使用效果非常有限,因为查询的一部分被执行两次。
为了避免这种双重执行,Oracle Database 11g 提供了另一种类型的分区修剪:联接过滤器修剪(也称为布隆过滤器修剪)。下面的执行计划是一个滤器修剪的示例(Id 2 和 5)。
SELECT * FROM t1, t2 WHERE t1.id = t2.id AND t1.mod = 42
---------------------------------------------------------------
| Id | Operation | Name | Pstart| Pstop |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | | |
|* 1 | HASH JOIN | | | |
| 2 | PART JOIN FILTER CREATE | :BF0000 | | |
| 3 | PARTITION HASH ALL | | 1 | 8 |
|* 4 | TABLE ACCESS FULL | T1 | 1 | 8 |
| 5 | PARTITION HASH JOIN-FILTER| |:BF0000|:BF0000|
| 6 | TABLE ACCESS FULL | T2 |:BF0000|:BF0000|
---------------------------------------------------------------
1 - access("T1"."ID"="T2"."ID")
4 - filter("T1"."MOD"=42)
简单的解释一下上面的执行计划。
Id 3 和 4:
扫描表 t1 的所有分区。
Id 2:
•根据Id 3返回的数据,创建布隆过滤器(BF0000)。
Id 5 和 6:
•布隆过滤器(BF0000),对表 t2 进行分区修剪,仅扫描包含相关数据的分区。
有趣的是,即使没有限制(在上一种情况下,限制为 t1.mod=42),查询优化器(CBO)也可能选择布隆过滤器修剪。这可能是因为使用布隆过滤器修剪的开销非常小,即使不进行修剪,相关的开销也微乎其微。所以如果要禁用此功能,初始化参数_bloom_pruning_enabled必须设置为 FALSE。
结论
本文讨论了两个ORACLE数据库借助布隆过滤器实现的优化技术:并行联接和联接过滤器修剪。两者都致力于提高特定类型的数据量非常大的 SQL 语句的处理性能。即使不是每个人都能够利用它们,这些优化对于越来越大的数据库非常重要。希望在未来的版本中,越来越多的这样的优化被开发出来。