Truy vấn SQL nào nhanh hơn? Lọc theo tiêu chí Tham gia hoặc mệnh đề Where?


98

So sánh 2 truy vấn này. Đặt bộ lọc vào tiêu chí nối hoặc trong WHEREmệnh đề có nhanh hơn không. Tôi luôn cảm thấy rằng nó nhanh hơn trên các tiêu chí tham gia vì nó làm giảm kết quả được đặt vào thời điểm sớm nhất có thể, nhưng tôi không biết chắc.

Tôi sẽ xây dựng một số bài kiểm tra để xem, nhưng tôi cũng muốn lấy ý kiến ​​về cái nào sẽ rõ ràng hơn để đọc.

Truy vấn 1

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
INNER JOIN  TableB b
        ON  x.TableBID = b.ID
WHERE       a.ID = 1            /* <-- Filter here? */

Truy vấn 2

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
        AND a.ID = 1            /* <-- Or filter here? */
INNER JOIN  TableB b
        ON  x.TableBID = b.ID

BIÊN TẬP

Tôi đã chạy một số bài kiểm tra và kết quả cho thấy rằng nó thực sự rất gần, nhưng WHEREmệnh đề thực sự nhanh hơn một chút! =)

Tôi hoàn toàn đồng ý rằng việc áp dụng bộ lọc vào WHEREmệnh đề sẽ hợp lý hơn, tôi chỉ tò mò về các hàm ý hiệu suất.

THỜI GIAN BỊ BỎ LỠ TRONG ĐÓ TIÊU CHÍ: 143016 mili giây
THỜI GIAN BỊ BỎ LỠ THỜI GIAN THAM GIA TIÊU CHÍ: 143256 mili giây

KIỂM TRA

SET NOCOUNT ON;

DECLARE @num    INT,
        @iter   INT

SELECT  @num    = 1000, -- Number of records in TableA and TableB, the cross table is populated with a CROSS JOIN from A to B
        @iter   = 1000  -- Number of select iterations to perform

DECLARE @a TABLE (
        id INT
)

DECLARE @b TABLE (
        id INT
)

DECLARE @x TABLE (
        aid INT,
        bid INT
)

DECLARE @num_curr INT
SELECT  @num_curr = 1
        
WHILE (@num_curr <= @num)
BEGIN
    INSERT @a (id) SELECT @num_curr
    INSERT @b (id) SELECT @num_curr
    
    SELECT @num_curr = @num_curr + 1
END

INSERT      @x (aid, bid)
SELECT      a.id,
            b.id
FROM        @a a
CROSS JOIN  @b b

/*
    TEST
*/
DECLARE @begin_where    DATETIME,
        @end_where      DATETIME,
        @count_where    INT,
        @begin_join     DATETIME,
        @end_join       DATETIME,
        @count_join     INT,
        @curr           INT,
        @aid            INT

DECLARE @temp TABLE (
        curr    INT,
        aid     INT,
        bid     INT
)

DELETE FROM @temp

SELECT  @curr   = 0,
        @aid    = 50

SELECT  @begin_where = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    WHERE       a.id = @aid
        
    SELECT @curr = @curr + 1
END
SELECT  @end_where = CURRENT_TIMESTAMP

SELECT  @count_where = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @curr = 0
SELECT  @begin_join = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
            AND a.id = @aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    
    SELECT @curr = @curr + 1
END
SELECT  @end_join = CURRENT_TIMESTAMP

SELECT  @count_join = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @count_where AS count_where,
        @count_join AS count_join,
        DATEDIFF(millisecond, @begin_where, @end_where) AS elapsed_where,
        DATEDIFF(millisecond, @begin_join, @end_join) AS elapsed_join

10
Tùy thuộc vào dữ liệu, tiêu chí WHERE và JOIN có thể trả về các tập kết quả khác nhau.
OMG Ponies

4
@OMG Ngựa con rất đúng, nhưng rất nhiều lần điều đó không đúng.
Jon Erickson

2
Tôi sẽ không gọi sự khác biệt dưới đây là 5% là sự khác biệt - chúng giống nhau. Bạn muốn có ý nghĩa cho sự khác biệt 2 %%, tốt hơn hãy chạy các bài kiểm tra 1000 lần để đảm bảo rằng iti không chỉ là ngẫu nhiên.
TomTom

Lợi ích nằm ở việc lọc dữ liệu trước khi tham gia, vì vậy nếu đó là x.ID thì bạn sẽ có nhiều khả năng thấy sự cải thiện hơn so với a.ID
MikeT

Câu trả lời:


65

Về hiệu suất, chúng giống nhau (và tạo ra các kế hoạch giống nhau)

Về mặt logic, bạn nên thực hiện thao tác vẫn có ý nghĩa nếu bạn thay thế INNER JOINbằng a LEFT JOIN.

Trong trường hợp của bạn, nó sẽ trông như thế này:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
        AND a.ID = 1
LEFT JOIN
        TableB b
ON      x.TableBID = b.ID

hoặc cái này:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
LEFT JOIN
        TableB b
ON      b.id = x.TableBID
WHERE   a.id = 1

Truy vấn trước đây sẽ không trả về bất kỳ kết quả phù hợp thực tế nào a.idkhác 1, vì vậy cú pháp sau (với WHERE) nhất quán hơn về mặt logic.


Khi tôi vẽ các bộ, tôi hiểu tại sao trường hợp thứ hai lại phù hợp hơn. Trong truy vấn trước đây, ràng buộc a.id = 1chỉ áp dụng cho giao lộ, không áp dụng cho phần bên trái ngoại trừ giao lộ.
FtheBuilder,

1
Trong ví dụ đầu tiên có thể có các hàng ở đó a.id != 1, còn lại sẽ chỉ có hàng ở đâu a.id = 1.
FtheBuilder,

1
Ngôn ngữ của bạn không rõ ràng. "Về mặt logic, bạn nên làm cho hoạt động vẫn có ý nghĩa nếu ..." và "logic hơn nhất quán" không có ý nghĩa. Bạn có thể vui lòng diễn đạt lại được không?
philipxy,

24

Đối với các liên kết bên trong, không quan trọng bạn đặt tiêu chí của mình ở đâu. Trình biên dịch SQL sẽ chuyển đổi cả hai thành một kế hoạch thực thi trong đó quá trình lọc xảy ra bên dưới phép nối (nghĩa là như thể biểu thức bộ lọc xuất hiện là trong điều kiện nối).

Các phép nối bên ngoài là một vấn đề khác, vì vị trí của bộ lọc thay đổi ngữ nghĩa của truy vấn.


Vì vậy, trong phép nối bên trong trước tiên nó tính toán bộ lọc và sau đó kết hợp đầu ra của bộ lọc với bảng khác hay trước tiên nó nối hai bảng và sau đó áp dụng bộ lọc?
Ashwin

@Remus Rusanu - bạn có thể vui lòng giải thích thêm về cách ngữ nghĩa được thay đổi trong trường hợp Outer-join không? Tôi nhận được các kết quả khác nhau dựa trên vị trí của bộ lọc, nhưng không thể hiểu tại sao
Ananth

3
@Ananth với một phép nối bên ngoài, bạn nhận được NULL cho tất cả các cột của bảng đã nối mà điều kiện JOIN không khớp. Các bộ lọc sẽ không đáp ứng NULL và loại bỏ các hàng, biến phép nối OUTER có hiệu lực thành phép nối INNER.
Remus Rusanu

@Ananth Tôi đã đạt được tối ưu hóa cần thiết của mình dựa trên nhận xét của bạn. Thay đổi của tôi là từ WHERE x.TableAID = a.ID hoặc x.TableAID là rỗng thành ON x.TableAID = a.ID. Việc thay đổi vị trí của bộ lọc trên kết nối OUTER cho phép trình biên dịch biết là Lọc rồi Tham gia chứ không phải Tham gia rồi Lọc. Nó cũng có thể sử dụng chỉ mục trên cột đó vì nó không phải khớp với Null. Phản hồi truy vấn đã thay đổi từ 61 giây thành 2 giây.
Ben Gripka

10

Theo như hai phương pháp đi.

  • JOIN / ON là để tham gia các bảng
  • WHERE dùng để lọc kết quả

Trong khi bạn có thể sử dụng chúng theo cách khác nhau, nó luôn có vẻ như là một mùi đối với tôi.

Đối phó với hiệu suất khi nó là một vấn đề. Sau đó, bạn có thể xem xét "tối ưu hóa" như vậy.


2

Với bất kỳ trình tối ưu hóa truy vấn nào, chúng đều giống hệt nhau.


Tôi khá chắc chắn rằng, với bất kỳ khối lượng công việc thực tế nào, chúng không giống nhau. Nếu bạn gần như không có dữ liệu, thì câu hỏi là vô giá trị.
eKek0

2
Kiểm tra nó dưới khối lượng công việc thực tế. Về cơ bản - nếu chúng tạo ra cùng một kế hoạch thực hiện, chúng ... giống hệt nhau về hiệu suất. Ít nhất đối với trường hợp bình thường / đơn giản (tức là không phải là người tham gia 14 bảng) Tôi khá chắc chắn rằng họ là giống hệt nhau;)
TomTom

1

Trong postgresql chúng giống nhau. Chúng tôi biết điều này bởi vì nếu bạn thực hiện explain analyzetrên từng truy vấn, kế hoạch sẽ giống nhau. Lấy ví dụ sau:

# explain analyze select e.* from event e join result r on e.id = r.event_id and r.team_2_score=24;

                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.045..0.047 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.009..0.010 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.017..0.017 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.008 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.182 ms
 Execution time: 0.101 ms
(10 rows)

# explain analyze select e.* from event e join result r on e.id = r.event_id where r.team_2_score=24;
                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.027..0.029 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.010..0.011 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.010..0.010 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.007 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.140 ms
 Execution time: 0.058 ms
(10 rows)

Cả hai đều có cùng chi phí tối thiểu và tối đa cũng như cùng một kế hoạch truy vấn. Ngoài ra, hãy lưu ý rằng ngay cả trong truy vấn hàng đầu, team_score_2 được áp dụng làm 'Bộ lọc'.


0

Thực sự không chắc rằng vị trí của phép nối này sẽ là yếu tố quyết định hiệu suất. Tôi không quen thuộc lắm với việc lập kế hoạch thực thi cho tsql, nhưng có khả năng chúng sẽ được tối ưu hóa tự động cho các kế hoạch tương tự.


0

Quy tắc # 0: Chạy một số điểm chuẩn và xem! Cách duy nhất để thực sự biết cái nào sẽ nhanh hơn là thử nó. Các loại điểm chuẩn này rất dễ thực hiện bằng cách sử dụng trình biên dịch SQL.

Ngoài ra, hãy kiểm tra kế hoạch thực thi cho truy vấn được viết bằng JOIN và với mệnh đề WHERE để xem những điểm khác biệt nào nổi bật.

Cuối cùng, như những người khác đã nói, hai điều này nên được xử lý giống nhau bởi bất kỳ trình tối ưu hóa tốt nào, bao gồm cả công cụ được tích hợp sẵn trong SQL Server.


Nhưng chỉ cho phép nối bên trong. Tập hợp kết quả sẽ rất khác nhau đối với các phép nối ngoài.
HLGEM

Tất nhiên. May mắn thay, ví dụ được cung cấp sử dụng các phép nối bên trong.
3Dave

1
Thật không may, câu hỏi là về liên kết chứ không phải liên kết bên trong.
Paul

Vâng, David, câu hỏi là về sự tham gia. Mẫu hỗ trợ câu hỏi sử dụng các phép nối bên trong.
Paul

0

Nó có nhanh hơn không? Hãy thử nó và xem.

Cái nào dễ đọc hơn? Điều đầu tiên đối với tôi có vẻ "đúng" hơn, vì điều kiện đã di chuyển thực sự không liên quan gì đến phép nối.


0

Tôi đoán đó là điều đầu tiên, bởi vì nó tạo ra một bộ lọc cụ thể hơn trên dữ liệu. Nhưng bạn nên xem kế hoạch thực thi , cũng như với bất kỳ tối ưu hóa nào, bởi vì nó có thể rất khác nhau về kích thước dữ liệu, phần cứng máy chủ, v.v.

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.