使用ORA_HASH对查询结果进行分区。

huangapple go评论74阅读模式
英文:

partitioning the query results with ORA_HASH

问题

Sure, here are the translated parts of your text:

我在RDBMS(Oracle 12+)中有几百万行数据,使用Spark(3+)将数据读入多个分区。但是,由于Oracle服务器由于TEMP表空间完全消耗而导致查询执行被中止(不同于此帖子的问题),查询执行被中止。为简单起见,假设我在Spark的配置中定义了8个分区,并且每个Spark执行程序都与其自己的分区一起工作。我们的SQL查询的形式是:

select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t

不关心底层使用的哈希算法是什么,以及它是否是确定性的,或者哈希值是否均匀分布。我的问题是:

  1. 这在内部是如何工作的,即Oracle服务器首先是否对列c的所有值应用哈希函数,然后仅为哈希值为0的那些行提取列ab的值或者Oracle服务器首先提取所有列,即列abc,然后基于c的哈希值,仅返回列ab的相应值?
  2. 当Spark并行化查询以使每个分区都获得其自己的数据时,我认为它是这样做的 - 每个执行程序选择基本查询,将其包装为其执行程序编号,例如,执行程序#3将执行查询,该查询与以下内容有些相似 -
select a, b from (select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t) where hashedID = 3

这个理解是正确的吗?如果是这样,那么由执行程序启动的每个JDBC连接都会独立计算哈希值。这将导致冗余计算。这个理解是正确的吗?

我尝试了解ORA_HASH的工作原理,但没有找到有关如何在计算ORA_HASH的查询上进行的投影的解释。因此,问题#1。

英文:

I have few million rows in the RDBMS (Oracle 12+) and i'm using Spark (3+) to read the data into multiple partitions. However, the query execution is getting aborted by the Oracle server due to TEMP tablespace being consumed completely (different issue than this post). For simplicity, assume that I have 8 paritions defined in Spark's config and each Spark executor works with its own partition. Our SQL query is of the form :

select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t

I am not concerned about which hash algorithm is used under the hood and if its deterministic or if hash values are evenly distributed. My questions are :

  1. How does this work internally i.e. Does the Oracle server first applies the hash function to all values of column c and then pull values of column a and b for only those rows whose hash value is 0 OR does the Oracle server pull all columns first i.e. column a, b and c and then based on c's hash, return only the corresponding values of column a and b?
  2. When Spark parallalizes the query such that every partition gets its own set of data, I presume it is done something like this - every executor picks up the base query, wraps it with its executor number e.g. executor#3 will execute query which is somewhat like -
select a, b from (select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t) where hashedID = 3

is this correct understanding? If so, then every JDBC connection initiated by executor calculates the hash independent of other executors. This would lead to redundant computations. Is this correct?

I did try to understand how ORA_HASH works, but didn't find an explanation about how the projection made on the query which computes ORA_HASH works internally. Hence the question#1.

答案1

得分: 2

Oracle首先检索由您的WHERE(和/或JOIN)子句标识的行,然后按照您的SELECT子句指定的列进行处理。因此,这个查询:

select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t

将提取Table_1表中的所有行(包括列ab),然后对列c应用CAST,然后应用ORA_HASH。所以,对于您的第一个问题的回答是:

"Oracle服务器首先对列c的所有值应用哈希函数,然后仅提取哈希值为0的那些行的列a和b的值,还是Oracle服务器首先提取所有列,即列a、b和c,然后根据c的哈希返回相应的列a和b的值?"

后者是正确的。

当您在此查询外再加一层包装时:

select a, b from (select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t) where hashedID = 3

Oracle将把hashID = 3的谓词推入视图并重写为:

select t.a, t.b from Table_1 t where ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) = 3

这将扫描表的100%行,对列c应用CAST然后ORA_HASH函数,然后过滤掉那些不等于3的行,并从筛选后的集合中返回列a和b。

因此,您的每个线程都会产生I/O开销,包括对100%行的全表扫描和对CAST/ORA_HASH工作的CPU开销,尽管它只请求一部分行。这是非常低效的,像这样进行蛮力提取的应用程序会对数据库健康造成很多问题(数据库管理员绝对不喜欢它们)。您绝对是正确的,这会进行冗余的计算工作。当您考虑到扫描本身通常是并行的全表扫描(使用直接路径读取)时,同时进行的10、20、30个以上的应用程序线程会对存储阵列产生聚合I/O压力,从而导致系统中的I/O响应降低。这种整体方法是错误的,我们(我的组织中的数据库管理员)会迅速关闭它,每当我们发现开发人员使用足够多的线程以至于它影响到我们的数据库健康时。

在最佳情况下,并行提取的最佳方式是对表进行哈希分区(使用Oracle的分区,而不是Sparks),然后使用扩展分区命名对每个哈希分区执行SELECT。但是很少有应用程序会具有这种复杂性。其他选项包括LIST分区,每个分区只有一个值,您的应用程序可以为每个值发出一个线程。或者使用RANGE,您可以轮流使用分区值,直到完成。无论如何,将Oracle的分区与提取进行对齐的优点是,然后每个线程只读取属于它的行,而不是冗余地读取其他每个线程的行。

另一种方法是让单个会话执行并行的CTAS(CREATE TABLE AS SELECT),使用无类型的列列表和分区子句来创建一个与您想要使用的分布方法相符的分区工作表,然后您可以使用它们的WHERE子句谓词将并发线程释放到相应的工作表分区。当然,这种方法的缺点是它会增加数据库的额外空间需求和写入成本。

最后,一些聪明的人可能会考虑基于函数的索引。虽然这将解决冗余问题并消除数据库的负载问题,但对于您的应用程序来说,它的性能非常差,因为100%的索引条目都需要读取表块,这比扫描表(或表分区)要慢得多。因此,将提取线程与(Oracle的)分区对齐实际上是最佳解决方案。

英文:

Oracle first retrieves the rows identified by your WHERE (and/or JOIN) clauses and then subsequently processes the columns as specified by your SELECT clause. Therefore, this:

select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t

Will pull 100% of the rows in Table_1 (which include columns a and b) and apply the CAST, then the ORA_HASH to column c. So, in answer to your first question:

"Does the Oracle server first applies the hash function to all values of column c and then pull values of column a and b for only those rows whose hash value is 0 OR does the Oracle server pull all columns first i.e. column a, b and c and then based on c's hash, return only the corresponding values of column a and b"

The latter is correct.

When you add another wrapper around this:

select a, b from (select t.a, t.b, ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) as hashedID from Table_1 t) where hashedID = 3

Oracle will push the hashID = 3 predicate into the view and rewrite it as:

select t.a, t.b from Table_1 t where ORA_HASH(CAST((t.c) AS NUMBER(38)), #{8 - 1}) = 3

This will scan 100% of the table's rows, applying the CAST then ORA_HASH functions to column c, and then filter out any rows where this doesn't equal 3, then return columns a and b from the filtered set.

So, each of your threads incurs the I/O penalty of a full table scan + the CPU penalty of the CAST/ORA_HASH work on 100% of the rows, even though it is only asking for a slice of rows. This is very inefficient and applications that do brute-force extracts like this cause lots of problems for database health (DBAs do not like them). You are absolutely right that this does redundant computational work. When you add that the scans themselves are typically parallelized full table scans (using direct path read) the aggregate I/O pressure on your storage arrays from 10, 20, 30+ application threads doing this concurrently can be overwhelming and cause I/O response degradation across the system. This whole approach is wrong-headed and we (DBAs in my org) are quick to shut it down anytime we find developers using it with enough threads that it compromises our database health.

Optimally the best way to parallelize and extract is to hash partition (Oracle's partitioning, not Sparks) the table and then fire up an SELECT against each hash partition using extended partition naming. But rarely are apps going to have this complexity. Other options are LIST partitioning with only one value per partition, and your app can issue a thread per value. Or RANGE and you round-robin through the partition values until you're done. However you do it, the advantage of aligning Oracle's partitioning with your extracts is that then each thread is only reading rows that belong to it, rather than redundantly reading every other thread's rows also.

Another approach is to have a single session do a parallelized CTAS (CREATE TABLE AS SELECT) with an untyped column list and a partitioning clause to create a partitioned work table that aligns with the distribution method you want to use, then you can unleash the concurrent threads using their WHERE clause predicates to prune to their respective partitions of this work table. Of course the downside to this is the extra space requirement this involves on the database, and the cost of writing it.

Lastly, some ingenious folks might consider a function-based index. While that would resolve the redundancy problem and remove the load concern for the database, it will perform very, very poorly for your application as 100% of the index entries will require a table block read, and that is hundreds of times slower than scanning a table (or table partition). So aligning extract threads with (Oracle's) partitioning is really the best solution.

huangapple
  • 本文由 发表于 2023年6月6日 02:09:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76408969.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定