启用NOT NULL 约束条件 与 HASH JOIN / FILTER

本文通过实验对比了SQL查询中字段是否设置NOT NULL约束对执行计划的不同影响,发现有NOT NULL约束时Oracle偏好使用hashjoin,而无约束时则倾向于filter优化的nestloop。

最近在书上看到一个说法,字段有无not null 约束对于查询方式的选择有影响,但未给出具体的实例。于是秉承自己动手丰衣足食的思想,做了一个简单的实验来看看到底有什么不同。

create table A
(
  id   NUMBER not null,
  name VARCHAR2(10) not null
)
insert into a values(1,'aa');
insert into a values(2,'bb');

create table B
(
  id   NUMBER not null,
  name VARCHAR2(10) not null
)
insert into b values(1,'aa');
insert into b values(2,'bb');
然后执行一个not in 语句,并且看看这个SELECT 的执行计划:

select * from a where (id,name) not in 
(select id,name from b where a.id=b.id and a.name=b.name)
执行计划如下:

SELECT STATEMENT, GOAL = ALL_ROWS			7	2	80	6	
 HASH JOIN ANTI			7	2	80	6	"A"."ID"="B"."ID" AND "A"."NAME"="B"."NAME"
  TABLE ACCESS FULL	JAC	A	3	2	40	3	
  TABLE ACCESS FULL	JAC	B	3	2	40	3	

清楚看到在启用了not null约束的表中,oracle选择了hash join。

然后将a,b 两表 id字段的not null 约束去掉:

sql>alter table a modify id null;
sql>alter table b modify id null;

再看看同一个查询的执行计划:

SELECT STATEMENT, GOAL = ALL_ROWS			6	1	20	6	
 FILTER							
  TABLE ACCESS FULL	JAC	A	3	2	40	3	
  TABLE ACCESS FULL	JAC	B	3	1	20	3	

可以看到的是,oracle这次执行的是filter, 类似于一种优化的nest loop。


经过这一次实验证明对于not null 约束条件的 有无,决定了oracle对待not in查询的态度。 当有not null约束 时,oracle喜欢用hash join,相反,当字段无not null 约束时,oracle貌似不能使用hash join 而偏向于选择filter。注:如果hash join只能用于等值连接,那么由于 null 值的等值判断返回的是unknown,因此hash join的确是不能用在有null值的场景中。

另外,not exists 无论字段是否有not null 约束,oracle都是使用 hash join.



### Map Join 在 Hive、Spark 和 Hadoop 中的作用实现 Map Join 是一种优化技术,用于在大数据处理中减少 Reduce 阶段的开销,从而提升查询性能。它主要适用于一个大表和一个小表进行 Join 操作的场景。小表会被加载到内存中,然后在 Map 阶段直接大表进行匹配,避免了 Shuffle 过程,从而减少了执行时间。 #### Hive 中的 Map Join Hive 支持 Map Join,并且可以通过参数配置自动将普通 Join 转换为 Map Join。当 Hive 检测到参 Join 的多个表中,有 n-1 个表的总大小小于设定的阈值时,会自动将该 Join 操作转换为 Map Join [^4]。这一过程不需要用户手动指定,但也可以通过 HiveQL 中的 Hint 显式控制 Map Join 的使用。 例如: ```sql SELECT /*+ MAPJOIN(small_table) */ * FROM large_table JOIN small_table ON large_table.id = small_table.id; ``` Hive 提供了一些参数来调整 Map Join 的行为,如 `hive.mapjoin.smalltable.filesize`,该参数定义了可以参 Map Join 的小表的最大大小,默认值通常为 25MB。建议将小表的大小控制在 1GB 以下,以确保内存不会成为瓶颈 [^3]。 此外,Hive 提供了一个开关参数 `hive.ignore.mapjoin.hint`,如果设置为 `true`,则会忽略所有 MapJoin Hint,这通常由集群运维人员根据实际情况决定是否启用 [^3]。 #### Spark 中的 Map Join 在 Spark 中,Map Join 并不是显式的操作,而是通过广播变量(Broadcast Variable)机制实现的。当其中一个表足够小时,可以将其广播到所有工作节点的内存中,然后在 Map 阶段直接进行 Join 操作,类似于 Hive 的 Map Join。 例如,在 Spark SQL 中可以通过如下方式显式广播小表: ```scala val broadcastedSmallDF = spark.sparkContext.broadcast(smallDF.collect()) val result = largeDF.filter(row => { val smallSet = broadcastedSmallDF.value.toSet smallSet.contains(row.getAs[Int]("id")) }) ``` Spark 也会自动优化 Join 操作,如果检测到某个表足够小,则会触发广播 Join(Broadcast Hash Join),这是 Spark Catalyst 优化器的一部分功能。广播 Join 可以显著减少 Shuffle 数据量,提高查询性能。 #### Hadoop MapReduce 中的 Map Join 在传统的 Hadoop MapReduce 环境中,Map Join 的实现需要手动编写代码来完成。具体步骤包括读取小表并将其加载到内存中(通常是 HashMap),然后在 Mapper 中对大表进行遍历,并查找内存中的匹配项。 示例代码如下: ```java public class MapJoinMapper extends Mapper<LongWritable, Text, Text, Text> { private Map<String, String> smallTable = new HashMap<>(); @Override protected void setup(Context context) throws IOException { // 从分布式缓存中加载小表数据 Path[] files = DistributedCache.getLocalCacheFiles(context.getConfiguration()); for (Path file : files) { BufferedReader reader = new BufferedReader(new FileReader(file.toString())); String line; while ((line = reader.readLine()) != null) { String[] parts = line.split(","); smallTable.put(parts[0], parts[1]); } reader.close(); } } @Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] fields = value.toString().split(","); String id = fields[0]; if (smallTable.containsKey(id)) { context.write(new Text(id), new Text(smallTable.get(id) + "\t" + fields[1])); } } } ``` 在上述代码中,小表通过 `DistributedCache` 加载到每个节点的内存中,随后在 Mapper 中完成 Join 操作,整个过程无需经过 Reducer。 #### Map Join 的适用场景 - **Hive**:适合用于数据仓库环境中结构化查询任务,尤其是业务报表分析,能够提供稳定的并发性能。 - **Spark**:更适合复杂的数据分析任务,特别是需要多次迭代或实时性要求较高的场景。 - **Hadoop MapReduce**:虽然支持 Map Join,但由于其基于磁盘的执行模型,整体性能不如 Spark,通常用于离线批处理任务。 #### 性能调优建议 - 在 Hive 中合理设置 `hive.mapjoin.smalltable.filesize`,推荐控制在 1GB 以下。 - 对于 Spark,确保小表可以完全放入内存,避免 OOM 错误。 - 在 Hadoop MapReduce 中,注意内存管理和分布式缓存的正确使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值