Tham gia bên ngoài đàn áp sử dụng Index?


7

Tôi có một chương trình máy khách đang thực hiện một truy vấn đối với Chế độ xem bên ngoài nối một bảng này sang bảng khác. Hiệu suất rất tệ và tôi đã cố gắng điều chỉnh nó bằng cách thêm chỉ mục đúng. Truy vấn trong câu hỏi chỉ thực sự sử dụng bảng thứ hai, vì vậy tôi đã trực tiếp kiểm tra bảng đó.

Tôi đã tìm thấy (một số) chỉ mục hoạt động tốt cho truy vấn đối với bảng nhưng khi tôi chuyển nó sang sử dụng Chế độ xem, chúng đã dừng sử dụng bất kỳ chỉ mục nào và thay vào đó chỉ quét toàn bộ trên cả hai bảng. Vì các bảng này lớn (2-3 triệu hàng mỗi cái) nên điều này rất chậm.

Để đơn giản kiểm tra, tôi đã thay đổi truy vấn để bỏ qua và chỉ kết hợp phép nối ngoài vào chính truy vấn đó. Điều này đã tái tạo thành công vấn đề, nhưng để lại bí ẩn về lý do tại sao tham gia bên ngoài sẽ không sử dụng các chỉ mục.

Đây là bảng, với tất cả các chỉ mục tôi đã thêm trong khi kiểm tra:

  CREATE TABLE TEST_DATA 
   (ID NUMBER(11,0)  PRIMARY KEY, 
    FORMATTED_RESULT VARCHAR2(255 BYTE), 
    F_RESULT NUMBER, 
    IDNUM NUMBER(11,0), 
    IDNUM_DESCRIPTION VARCHAR2(128 BYTE), 
    LAB_NUMBER NUMBER(11,0), 
    SEQ_NUMBER NUMBER(11,0),
    ORDERNO NUMBER(11,0),
    SUPPL_FORMATTED_RESULT VARCHAR2(255 BYTE), 
    SUPPL_IDNUM NUMBER(11,0), 
    SUPPL_IDNUM_DESCRIPTION VARCHAR2(128 BYTE), 
    SUPPL_UNIT VARCHAR2(16 BYTE)
   ) ;

  CREATE UNIQUE INDEX TEST_LN_SQN_ORDER ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER, ORDERNO) ;
  CREATE INDEX TEST_LN_SQN ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER) ;
  CREATE INDEX TD_CUIDD_CUFR ON TEST_DATA (UPPER(COALESCE(SUPPL_IDNUM_DESCRIPTION,IDNUM_DESCRIPTION)), UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))) ;
  CREATE INDEX TD_UFR_IDN ON TEST_DATA (UPPER(FORMATTED_RESULT), IDNUM) ;
  CREATE INDEX TD_UIDD_UFR ON TEST_DATA (UPPER(IDNUM_DESCRIPTION), UPPER(FORMATTED_RESULT)) ;
  CREATE INDEX TD_CUFR_CIDN_SN_LN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM), SEQ_NUMBER, LAB_NUMBER) ;
  CREATE INDEX TD_SN_LN_CUFR_CIDN ON TEST_DATA (SEQ_NUMBER, LAB_NUMBER, UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;
  CREATE INDEX TD_CUFR_CIDN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;

Đây là bảng khác (bảng mà chúng tôi không thực sự sử dụng cho truy vấn này)

  CREATE TABLE REQUEST_INFO 
   (NUMBER(11,0) PRIMARY KEY, 
    CHARGE_CODE VARCHAR2(32 BYTE), 
    LAB_NUMBER NUMBER(11,0), 
    SEQ_NUMBER NUMBER(11,0)
   ) ;

  CREATE INDEX RI_LN_SN ON REQUEST_INFO (LAB_NUMBER, SEQ_NUMBER) ;
  CREATE INDEX RI_SN_LN ON REQUEST_INFO (SEQ_NUMBER, LAB_NUMBER) ;

Vì vậy, trước tiên, đây là truy vấn trực tiếp vào bảng duy nhất, sử dụng thành công một trong các chỉ mục.

-- GOOD, Uses index : TD_CUFR_CIDN_SN_LN
select td.LAB_NUMBER 
from test_DATA td 
where UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376'
  and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549 
;

Bây giờ đây là truy vấn sử dụng cả hai bảng với một phép nối bên trong . Điều này cũng sử dụng các chỉ mục và chạy nhanh.

-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER  
from REQUEST_INFO RI 
JOIN TEST_DATA TD ON  TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
  and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549 

Và đây là cùng một truy vấn với Left Outer Join, vì nó được viết trong dạng xem. Điều này KHÔNG sử dụng bất kỳ chỉ mục nào và chạy rất chậm.

-- BAD, does not use indexes
select TD.LAB_NUMBER 
from REQUEST_INFO RI 
LEFT JOIN TEST_DATA TD ON  TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
  and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549 
;

Bây giờ trước khi bất cứ ai nói nó: truy vấn này thực sự là một cách logic giống với trước đó. Điều này là do mệnh đề WHERE đang lọc trên các cột từ bảng bên ngoài (TD), điều này có hiệu quả / hợp lý biến một phép nối ngoài thành một phép nối bên trong (đây là lý do tại sao điều kiện xảy ra trong mệnh đề ON so với mệnh đề WHERE).

Bây giờ, chỉ để thêm vào sự kỳ lạ, tôi quyết định xem điều gì sẽ xảy ra nếu tôi làm cho sự ép buộc từ bên ngoài đến bên trong bùng nổ hơn:

-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER 
from REQUEST_INFO RI 
LEFT JOIN TEST_DATA TD ON  TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
  and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549 
and TD.LAB_NUMBER IS NOT NULL
;

Thật đáng kinh ngạc, điều này đã làm việc!

Vì vậy, câu hỏi ở đây là, 1) TẠI SAO Oracle không tự tìm ra điều này?

Và 2) Có một số cài đặt hoặc Chỉ mục, v.v. mà tôi có thể tạo sẽ khiến Oracle tìm ra điều này một cách chính xác và sử dụng các chỉ mục không?

Xem xét bổ sung:

  • Chế độ xem được sử dụng bởi một loạt các truy vấn và ứng dụng khách khác, vì vậy tôi không thể thay đổi nó thành một tham gia bên trong cho một truy vấn này.

  • Máy khách đang tạo truy vấn, do đó rất khó / không thể thay đổi truy vấn với các điều kiện trường hợp đặc biệt kỳ quặc như: " Sử dụng chế độ xem này cho dữ liệu này, trừ khi bạn chỉ cần các cột này từ một bảng này, sau đó sử dụng một cột khác xem "hoặc" khi bạn cần các cột này và chỉ các cột này từ một bảng này, sau đó thêm 'IS NOT NULL' vào mệnh đề WHERE "

Bất kỳ đề xuất hoặc hiểu biết sẽ được hoan nghênh.


CẬP NHẬT: Tôi cũng đã thử nó trên Oracle 11g, tôi nhận được kết quả chính xác ở đó.


Mỗi yêu cầu, đây là đầu ra Giải thích Kế hoạch, đầu tiên là phiên bản tốt, nơi nó sử dụng các chỉ mục:

Rows      Plan                                       COST    Predicates
        3 SELECT STATEMENT                                 8 
        3  HASH JOIN                                       8 Access:TD.LAB_NUMBER=RI.LAB_NUMBER AND TD.SEQ_NUMBER=RI.SEQ_NUMBER
        3   NESTED LOOPS                                   8 
             STATISTICS COLLECTOR
        3     INDEX RANGE SCAN TD_CUFR_CIDN_SN_LN          4 Access:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549, Filter:TD.LAB_NUMBER IS NOT NULL
        1    INDEX RANGE SCAN RI_SN_LN                     2 Access:TD.SEQ_NUMBER=RI.SEQ_NUMBER AND TD.LAB_NUMBER=RI.LAB_NUMBER
        1   INDEX FAST FULL SCAN RI_SN_LN                  2

Và bây giờ là phiên bản xấu:

Rows      Plan                                       COST    Predicates
 31939030 SELECT STATEMENT                            910972
           FILTER                                             Filter:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549
 31939030   HASH JOIN OUTER                           910972 Access:TD.LAB_NUMBER(+)=RI.LAB_NUMBER AND TD.SEQ_NUMBER(+)=RI.SEQ_NUMBER
  6213479    TABLE ACCESS FULL REQUEST_INFO            58276
 56276228    TABLE ACCESS FULL TEST_DATA              409612

Bạn có thể thử sau khi chuyển đổi thứ tự bảng trong truy vấn trong đó "nó không hoạt động" không?
Raj

@JoeObish Tôi chưa thử gợi ý vì chúng quá nặng tay là một giải pháp cho tình huống này. Đối với kế hoạch giải thích, tôi đã có vấn đề bảo mật để xem xét. Tôi đã thay đổi tên trong các kịch bản ở trên, một kế hoạch giải thích sẽ khó chỉnh sửa hơn. Tôi sẽ xem những gì tôi có thể làm.
RBarryYoung

@Raj Bạn có nghĩa là chuyển đổi thứ tự của hai bảng và thay đổi TRÁI thành một kết nối bên ngoài ĐÚNG? Tôi có thể thử điều đó vào ngày mai ...
RBarryYoung

@Raj chuyển đổi thứ tự bảng không có hiệu lực.
RBarryYoung

@JoeObbish Tôi đã đăng Kế hoạch Giải thích.
RBarryYoung

Câu trả lời:


2

Đây chủ yếu là một câu trả lời một phần cho phần 1 với một số suy đoán. Bạn và tôi biết rằng truy vấn sau đây:

select TD.LAB_NUMBER 
from REQUEST_INFO RI 
LEFT JOIN TEST_DATA TD ON  TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
  and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;

Tương đương với truy vấn này:

select TD.LAB_NUMBER 
from REQUEST_INFO RI 
INNER JOIN TEST_DATA TD ON 
TD.LAB_NUMBER = RI.LAB_NUMBER 
AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
AND UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549;

Tuy nhiên, điều đó không có nghĩa là Oracle biết rằng hai truy vấn là tương đương nhau. Sự tương đương của hai truy vấn là cần thiết để Oracle có thể sử dụng TD_CUFR_CIDN_SN_LNchỉ mục. Những gì chúng ta đang hy vọng kiếm ở đây một là OUTER JOINđể INNER JOINchuyển đổi. Tôi đã không có nhiều may mắn khi tìm thấy thông tin tốt về điều này , vì vậy hãy xem các kế hoạch giải thích:

LAB_NUMBER

Thêm TD.LAB_NUMBER IS NOT NULLvào WHEREkhoản là một cách rất trực tiếp để cho Oracle biết rằng OUTER JOINđể INNER JOINchuyển đổi là có thể. Chúng ta có thể thấy rằng nó xảy ra bằng cách nhìn vào dòng được tô sáng. Tôi nghĩ rằng khá nhiều cột bất kỳ sẽ cho phép chuyển đổi, mặc dù chọn cột sai có thể thay đổi kết quả truy vấn.

Nếu chúng tôi thử một bộ lọc phức tạp hơn một chút, chẳng hạn như (TD.LAB_NUMBER IS NOT NULL OR TD.SEQ_NUMBER IS NOT NULL)chuyển đổi tham gia sẽ không xảy ra:

không tham gia chuyển đổi

Chúng ta có thể lý giải rằng đó OUTER JOINthực sự là một INNER JOINnhưng trình tối ưu hóa truy vấn có thể chưa được lập trình để làm điều đó. Trong truy vấn ban đầu, bạn có một COALESCE()biểu thức có thể quá phức tạp để trình tối ưu hóa truy vấn áp dụng chuyển đổi truy vấn.

Đây là một fiddle db cho một số ví dụ.

Đối với câu hỏi thứ hai, tôi không thể nghĩ ra cách nào để giải quyết vấn đề này. Bạn có thể thử tận dụng việc loại bỏ bảng . Như bạn đã nói truy vấn này thậm chí không yêu cầu REQUEST_INFObảng. Tuy nhiên, có một vài hạn chế:

Hiện tại có một vài hạn chế của việc loại bỏ bảng:

  • Các ràng buộc khóa chính-nhiều cột chính không được hỗ trợ.

  • Tham chiếu đến khóa tham gia ở nơi khác trong truy vấn sẽ ngăn việc loại bỏ bảng. Đối với phép nối bên trong, các khóa tham gia ở mỗi bên của phép nối là tương đương, nhưng nếu truy vấn chứa các tham chiếu khác đến khóa tham gia từ bảng có thể bị loại bỏ, điều này sẽ ngăn chặn việc loại bỏ. Một cách giải quyết là viết lại truy vấn để tham chiếu khóa tham gia từ bảng khác (chúng tôi nhận ra điều này không phải lúc nào cũng có thể).

Có lẽ có một cách để sử dụng nó cho vấn đề này nhưng tôi không thể giải quyết các hạn chế.


Thx, điều này khá phù hợp với những gì tôi đã thấy với các thử nghiệm khác nhau: Đối với thử nghiệm NULL một cột, nó chỉ ra điều đó, nhưng đối với nhiều cột tách rời khỏi cùng một bảng (thông qua OR hoặc COALESCE) thì không. Loại bỏ bảng sẽ không hoạt động trong trường hợp này (các khóa nhiều cột), vì vậy tôi sẽ phải đẩy chúng để thay đổi trình tạo truy vấn của máy khách.
RBarryYoung

-1

Thay thế Câu lệnh Coalesce bằng một ORcâu lệnh vì Thêm một hàm ở phía bên trái của mệnh đề Where sẽ không sử dụng chỉ mục trừ khi hàm bên trái được lập chỉ mục bằng cách sử dụng chỉ mục dựa trên hàm, vì vậy vui lòng thay đổi truy vấn như bên dưới. Chỉ mục riêng biệt trên SUPPL_FORMATTED_RESULTFORMATTED_RESULTnên có upperchức năng cho truy vấn bên dưới để sử dụng quyền truy cập chỉ mục.

Lưu ý: nếu có sự sai lệch trong dữ liệu và nếu số lượng bản ghi có các giá trị '491 (10) 376' và 40549 thì nhiều hơn nữa sẽ bỏ qua chỉ mục và sử dụng quét toàn bộ bảng.

select TD.LAB_NUMBER 
from REQUEST_INFO RI 
LEFT JOIN TEST_DATA TD ON  TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER 
where (UPPER(TD.SUPPL_FORMATTED_RESULT) ='491(10)376' or   
       UPPER(TD.FORMATTED_RESULT)='491(10)376')
  and  (TD.SUPPL_IDNUM =40549 or TD.IDNUM=40549); 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.