2011-11-18 24 views
8

sự hiểu biết của tôi là HASH JOIN chỉ có ý nghĩa khi một trong 2 bảng đủ nhỏ để vừa với bộ nhớ dưới dạng bảng băm.Oracle luôn sử dụng HASH JOIN ngay cả khi cả hai bảng đều lớn?

nhưng khi tôi đưa ra một truy vấn đến oracle, với cả hai bảng có hàng trăm triệu hàng, oracle vẫn đưa ra một kế hoạch giải thích băm tham gia. ngay cả khi tôi lừa nó với OPT_ESTIMATE (rows = ....) gợi ý, nó luôn luôn quyết định sử dụng HASH JOIN thay vì hợp nhất sắp xếp sắp xếp.

vì vậy tôi tự hỏi làm thế nào HASH JOIN có thể trong trường hợp cả hai bảng là rất lớn?

nhờ Yang

+0

Liệu kế hoạch giải thích hiển thị một chỉ số dao động quét?Bạn là chính xác, một bảng đầy đủ quét với hash tham gia sẽ tiêu thụ tải của bộ nhớ. Xin vui lòng gửi đầu ra kế hoạch giải thích đầy đủ –

+0

là các số liệu thống kê cho bảng cập nhật? –

Trả lời

20

Kết nối băm rõ ràng là hoạt động tốt nhất khi mọi thứ có thể vừa với bộ nhớ. Nhưng điều đó không có nghĩa là chúng không phải là phương pháp kết nối tốt nhất khi bảng không thể vừa với bộ nhớ. Tôi nghĩ rằng phương pháp tham gia thực tế khác chỉ là một kết hợp sắp xếp hợp nhất.

Nếu bảng băm không thể vừa với bộ nhớ, việc sắp xếp bảng cho phép sắp xếp sắp xếp hợp nhất cũng không thể vừa với bộ nhớ. Và hợp nhất tham gia cần phải sắp xếp cả hai bảng. Theo kinh nghiệm của tôi, băm là luôn luôn nhanh hơn so với phân loại, để tham gia và cho nhóm.

Nhưng có một số ngoại lệ. Từ số Oracle® Database Performance Tuning Guide, The Query Optimizer:

Tham gia băm thường hoạt động tốt hơn kết hợp hợp nhất sắp xếp. Tuy nhiên, loại hợp nhất tham gia có thể thực hiện tốt hơn so với băm tham gia nếu cả hai điều kiện sau đây tồn tại:

The row sources are sorted already. 
    A sort operation does not have to be done. 

thử nghiệm

Thay vì tạo ra hàng trăm triệu hàng, đó là dễ dàng hơn để buộc Oracle chỉ sử dụng một lượng bộ nhớ rất nhỏ.

Biểu đồ này cho thấy rằng băm tham gia làm tốt hơn kết hợp tham gia, ngay cả khi các bảng là quá lớn để phù hợp (giới hạn nhân tạo) bộ nhớ:

Hash vs Merge


Ghi chú

Đối điều chỉnh hiệu suất thường tốt hơn khi sử dụng byte so với số hàng. Nhưng kích thước "thực" của bảng là một điều khó đo lường, đó là lý do tại sao biểu đồ hiển thị các hàng. Các kích thước này xấp xỉ từ 0.375 MB đến 14 MB. Để kiểm tra lại xem các truy vấn này có thực sự ghi vào đĩa hay không, bạn có thể chạy chúng bằng/* + gather_plan_statistics */và sau đó truy vấn v $ sql_plan_statistics_all.

Tôi chỉ thử nghiệm các phép nối băm và kết hợp sắp xếp hợp nhất. Tôi đã không kiểm tra đầy đủ các vòng lặp lồng nhau vì phương thức kết nối đó luôn cực kỳ chậm với số lượng lớn dữ liệu. Như một kiểm tra sanity, tôi đã so sánh nó một lần với kích thước dữ liệu cuối cùng, và phải mất ít nhất vài phút trước khi tôi giết nó.

Tôi cũng đã thử nghiệm với các dữ liệu _area_sizes khác nhau, thứ tự và dữ liệu không theo thứ tự, và sự khác biệt khác nhau của cột nối (nhiều kết quả khớp hơn CPU, ít khớp hơn IO) và có kết quả tương đối giống nhau.

Tuy nhiên, kết quả khác nhau khi lượng bộ nhớ nhỏ một cách lố bịch. Với chỉ 32K sắp xếp | hash_area_size, kết hợp sắp xếp sắp xếp nhanh hơn đáng kể. Nhưng nếu bạn có quá ít bộ nhớ, bạn có thể có nhiều vấn đề đáng lo ngại hơn.

Vẫn còn nhiều biến khác cần xem xét, chẳng hạn như song song, phần cứng, bộ lọc hoa, v.v. Mọi người có thể đã viết sách về chủ đề này, tôi chưa thử nghiệm một phần nhỏ các khả năng. Nhưng hy vọng điều này là đủ để xác nhận sự đồng thuận chung rằng việc kết hợp băm là tốt nhất cho dữ liệu lớn.


Dưới đây là những kịch bản tôi đã sử dụng:

--Drop objects if they already exist 
drop table test_10k_rows purge; 
drop table test1 purge; 
drop table test2 purge; 

--Create a small table to hold rows to be added. 
--("connect by" would run out of memory later when _area_sizes are small.) 
--VARIABLE: More or less distinct values can change results. Changing 
--"level" to something like "mod(level,100)" will result in more joins, which 
--seems to favor hash joins even more. 
create table test_10k_rows(a number, b number, c number, d number, e number); 
insert /*+ append */ into test_10k_rows 
    select level a, 12345 b, 12345 c, 12345 d, 12345 e 
    from dual connect by level <= 10000; 
commit; 

--Restrict memory size to simulate running out of memory. 
alter session set workarea_size_policy=manual; 

--1 MB for hashing and sorting 
--VARIABLE: Changing this may change the results. Setting it very low, 
--such as 32K, will make merge sort joins faster. 
alter session set hash_area_size = 1048576; 
alter session set sort_area_size = 1048576; 

--Tables to be joined 
create table test1(a number, b number, c number, d number, e number); 
create table test2(a number, b number, c number, d number, e number); 

--Type to hold results 
create or replace type number_table is table of number; 

set serveroutput on; 

-- 
--Compare hash and merge joins for different data sizes. 
-- 
declare 
    v_hash_seconds number_table := number_table(); 
    v_average_hash_seconds number; 
    v_merge_seconds number_table := number_table(); 
    v_average_merge_seconds number; 

    v_size_in_mb number; 
    v_rows number; 
    v_begin_time number; 
    v_throwaway number; 

    --Increase the size of the table this many times 
    c_number_of_steps number := 40; 
    --Join the tables this many times 
    c_number_of_tests number := 5; 

begin 
    --Clear existing data 
    execute immediate 'truncate table test1'; 
    execute immediate 'truncate table test2'; 

    --Print headings. Use tabs for easy import into spreadsheet. 
    dbms_output.put_line('Rows'||chr(9)||'Size in MB' 
     ||chr(9)||'Hash'||chr(9)||'Merge'); 

    --Run the test for many different steps 
    for i in 1 .. c_number_of_steps loop 
     v_hash_seconds.delete; 
     v_merge_seconds.delete; 
     --Add about 0.375 MB of data (roughly - depends on lots of factors) 
     --The order by will store the data randomly. 
     insert /*+ append */ into test1 
     select * from test_10k_rows order by dbms_random.value; 

     insert /*+ append */ into test2 
     select * from test_10k_rows order by dbms_random.value; 

     commit; 

     --Get the new size 
     --(Sizes may not increment uniformly) 
     select bytes/1024/1024 into v_size_in_mb 
     from user_segments where segment_name = 'TEST1'; 

     --Get the rows. (select from both tables so they are equally cached) 
     select count(*) into v_rows from test1; 
     select count(*) into v_rows from test2; 

     --Perform the joins several times 
     for i in 1 .. c_number_of_tests loop 
      --Hash join 
      v_begin_time := dbms_utility.get_time; 
      select /*+ use_hash(test1 test2) */ count(*) into v_throwaway 
      from test1 join test2 on test1.a = test2.a; 
      v_hash_seconds.extend; 
      v_hash_seconds(i) := (dbms_utility.get_time - v_begin_time)/100; 

      --Merge join 
      v_begin_time := dbms_utility.get_time; 
      select /*+ use_merge(test1 test2) */ count(*) into v_throwaway 
      from test1 join test2 on test1.a = test2.a; 
      v_merge_seconds.extend; 
      v_merge_seconds(i) := (dbms_utility.get_time - v_begin_time)/100; 
     end loop; 

     --Get average times. Throw out first and last result. 
     select (sum(column_value) - max(column_value) - min(column_value)) 
      /(count(*) - 2) 
     into v_average_hash_seconds 
     from table(v_hash_seconds); 

     select (sum(column_value) - max(column_value) - min(column_value)) 
      /(count(*) - 2) 
     into v_average_merge_seconds 
     from table(v_merge_seconds); 

     --Display size and times 
     dbms_output.put_line(v_rows||chr(9)||v_size_in_mb||chr(9) 
      ||v_average_hash_seconds||chr(9)||v_average_merge_seconds); 

    end loop; 
end; 
/
0

Một băm join không nhất thiết phải phù hợp với toàn bộ bảng vào bộ nhớ, nhưng chỉ có các hàng mà phù hợp với điều kiện nơi của bảng đó (hoặc thậm chí chỉ là một băm + các rowid - Tôi không chắc về việc đó). Vì vậy, khi Oracle quyết định rằng tính chọn lọc của một phần của các điều kiện ảnh hưởng đến một trong các bảng là đủ tốt (tức là một vài hàng sẽ bị băm), nó có thể thích tham gia băm ngay cả đối với các bảng rất lớn.

3

Vì vậy, tôi tự hỏi làm thế nào là HASH THAM GIA thể trong trường hợp của cả hai bảng là rất lớn?

Nó sẽ được thực hiện trong nhiều lần: bảng điều khiển được đọc và được băm thành từng phần, bảng hàng đầu được quét nhiều lần.

Điều này có nghĩa rằng với băm bộ nhớ hạn chế tham gia quy mô tại O(N^2) khi hợp nhất tham gia quy mô tại O(N) (với Không sắp xếp cần thiết tất nhiên), và trên thực sự bảng lớn sáp nhập nhanh hơn so với băm tham gia. Tuy nhiên, các bảng nên thực sự lớn để lợi ích của đọc duy nhất sẽ thừa cân hạn chế truy cập không tuần tự, và bạn sẽ cần tất cả dữ liệu từ chúng (thường được tổng hợp).

Với kích thước RAM trên máy chủ hiện đại, chúng tôi đang nói về các báo cáo thực sự lớn về cơ sở dữ liệu thực sự lớn, phải mất hàng giờ để xây dựng chứ không phải thứ bạn thực sự thấy trong cuộc sống hàng ngày.

MERGE JOIN cũng có thể hữu ích khi bản ghi đầu ra bị giới hạn với rownum < N. Nhưng điều này có nghĩa là các tham số đầu vào phải được sắp xếp có nghĩa là cả hai đều được lập chỉ mục có nghĩa là có sẵn NESTED LOOPS và đó là những gì thường được trình tối ưu hóa chọn vì điều này hiệu quả hơn khi điều kiện kết nối được chọn lọc.

Với việc triển khai hiện tại, MERGE JOIN luôn quét và NESTED LOOPS luôn tìm kiếm, trong khi kết hợp thông minh hơn của cả hai phương pháp (được sao lưu theo thống kê) sẽ được ưu tiên.

Bạn có thể muốn đọc bài viết này trong blog của tôi:

Các vấn đề liên quan