แบบสอบถาม SQL ใดเร็วกว่า กรองเกณฑ์การเข้าร่วมหรือ Where clause?


99

เปรียบเทียบ 2 คำค้นหานี้ เร็วกว่าที่จะใส่ตัวกรองในเกณฑ์การเข้าร่วมหรือในส่วนWHEREคำสั่ง ฉันรู้สึกเสมอว่ามันเร็วกว่าเกณฑ์การเข้าร่วมเพราะมันจะลดผลลัพธ์ที่ตั้งไว้ในช่วงเวลาที่เร็วที่สุดเท่าที่จะเป็นไปได้ แต่ฉันไม่รู้แน่ชัด

ฉันจะสร้างแบบทดสอบเพื่อดู แต่ฉันก็อยากได้ความคิดเห็นที่จะอ่านได้ชัดเจนกว่าเช่นกัน

แบบสอบถาม 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? */

แบบสอบถาม 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

แก้ไข

ฉันทำการทดสอบบางอย่างและผลปรากฏว่ามันใกล้มากจริง ๆ แต่WHEREประโยคนั้นเร็วกว่าเล็กน้อย! =)

ฉันเห็นด้วยอย่างยิ่งว่าการใช้ตัวกรองกับWHEREประโยคนั้นเหมาะสมกว่าฉันแค่อยากรู้เกี่ยวกับผลกระทบด้านประสิทธิภาพ

เวลาที่ผ่านไปเมื่อเกณฑ์: 143016 ms
เวลาที่ผ่านไปเวลาเข้าร่วมเกณฑ์: 143256 ms

ทดสอบ

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
ขึ้นอยู่กับข้อมูลเกณฑ์ WHERE vs JOIN สามารถส่งคืนผลลัพธ์ที่แตกต่างกันได้
OMG Ponies

4
@OMG Ponies จริงมาก แต่หลายครั้งก็ไม่ได้เช่นกัน
Jon Erickson

2
ฉันจะไม่เรียกความแตกต่าง beelow 5% ว่าเป็นความแตกต่าง - มันเหมือนกัน คุณต้องการความสำคัญสำหรับความแตกต่าง 2 %% ให้ดีขึ้นเรียกใช้การทดสอบ 1,000 ครั้งเพื่อให้แน่ใจว่าไม่ใช่แค่สุ่ม
TomTom

ประโยชน์อยู่ที่การกรองข้อมูลก่อนเข้าร่วมดังนั้นหากเป็น x.ID คุณจะมีแนวโน้มที่จะเห็นการปรับปรุงมากกว่าการใช้ a ID
MikeT

คำตอบ:


66

ประสิทธิภาพที่ชาญฉลาดเหมือนกัน (และจัดทำแผนเดียวกัน)

ตามเหตุผลคุณควรทำการดำเนินการที่ยังคงมีความหมายหากคุณแทนที่INNER JOINด้วยไฟล์LEFT JOIN.

ในกรณีของคุณสิ่งนี้จะมีลักษณะดังนี้:

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

หรือสิ่งนี้:

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

แบบสอบถามเดิมจะไม่ส่งคืนการจับคู่จริงใดa.idๆ นอกเหนือจาก1นั้นดังนั้นไวยากรณ์หลัง (ด้วยWHERE) จึงมีความสอดคล้องกันมากขึ้น


เมื่อฉันวาดชุดฉันเข้าใจว่าเหตุใดกรณีที่สองจึงสอดคล้องกันมากขึ้น ในข้อความค้นหาเดิมข้อ จำกัดa.id = 1จะใช้กับจุดตัดเท่านั้นไม่ใช่ส่วนด้านซ้ายที่ไม่รวมจุดตัด
FtheBuilder

1
ในตัวอย่างแรกอาจจะมีแถวที่a.id != 1ที่ประสงค์อื่น ๆ a.id = 1ที่มีเฉพาะแถวที่
FtheBuilder

1
ภาษาของคุณไม่ชัดเจน "ตามหลักเหตุผลคุณควรทำให้การดำเนินการที่ยังคงมีเหตุผลถ้า ... " และ "มีเหตุผลสอดคล้องกันมากขึ้น" ไม่สมเหตุสมผล คุณช่วยเรียบเรียงใหม่ได้ไหม
philipxy

24

สำหรับการรวมภายในไม่สำคัญว่าคุณจะวางเกณฑ์ไว้ที่ใด คอมไพลเลอร์ SQL จะแปลงทั้งสองอย่างเป็นแผนการดำเนินการซึ่งการกรองเกิดขึ้นด้านล่างการรวม (เช่นถ้านิพจน์ตัวกรองปรากฏอยู่ในเงื่อนไขการรวม)

การรวมภายนอกเป็นคนละเรื่องเนื่องจากตำแหน่งของตัวกรองจะเปลี่ยนความหมายของแบบสอบถาม


ดังนั้นในการรวมภายในจะคำนวณตัวกรองก่อนแล้วจึงรวมเอาท์พุทของตัวกรองกับตารางอื่นหรือไม่ก่อนอื่นจะรวมสองตารางแล้วจึงใช้ตัวกรอง
Ashwin

@Remus Rusanu - คุณช่วยอธิบายให้ละเอียดได้ไหมว่าความหมายเปลี่ยนไปอย่างไรในกรณีของการเข้าร่วมภายนอก ฉันได้ผลลัพธ์ที่แตกต่างกันตามตำแหน่งของตัวกรอง แต่ไม่เข้าใจว่าทำไม
Ananth

3
@Ananth ด้วยการรวมภายนอกคุณจะได้รับ NULL สำหรับคอลัมน์ทั้งหมดของตารางที่เข้าร่วมซึ่งเงื่อนไขการเข้าร่วมไม่ตรงกัน ฟิลเตอร์จะไม่ตอบสนอง NULL และกำจัดแถวออกโดยเปลี่ยนการรวมภายนอกให้เป็นการรวมภายใน
Remus Rusanu

@Ananth ฉันได้รับการปรับให้เหมาะสมตามความคิดเห็นของคุณแล้ว การเปลี่ยนแปลงของฉันมาจาก WHERE x.TableAID = a.ID หรือ x.TableAID เป็นโมฆะเป็น ON x.TableAID = a.ID การเปลี่ยนตำแหน่งของตัวกรองในการรวมภายนอกทำให้คอมไพเลอร์รู้ว่าจะกรองแล้วเข้าร่วมแทนที่จะเข้าร่วมแล้วกรอง นอกจากนี้ยังสามารถใช้ดัชนีในคอลัมน์นั้นได้เนื่องจากไม่จำเป็นต้องตรงกับ Null การตอบสนองของข้อความค้นหาเปลี่ยนจาก 61 วินาทีเป็น 2 วินาที
Ben Gripka

10

เท่าที่ทั้งสองวิธีไป

  • JOIN / ON ใช้สำหรับการเข้าร่วมตาราง
  • WHERE ใช้สำหรับกรองผลลัพธ์

แม้ว่าคุณจะใช้มันแตกต่างกัน แต่ก็ดูเหมือนจะเป็นกลิ่นสำหรับฉันเสมอ

จัดการกับประสิทธิภาพเมื่อเกิดปัญหา จากนั้นคุณสามารถดู "การเพิ่มประสิทธิภาพ" ดังกล่าว


2

ด้วยเครื่องมือเพิ่มประสิทธิภาพการสืบค้นใด ๆ ที่สำคัญ .... พวกมันเหมือนกัน


ฉันค่อนข้างแน่ใจว่ากับภาระงานจริง ๆ มันไม่เหมือนกัน หากคุณแทบไม่มีข้อมูลคำถามก็ไร้ค่า
eKek0

2
ตรวจสอบภายใต้ภาระงานจริง โดยทั่วไป - หากพวกเขาสร้างแผนการดำเนินการเดียวกันพวกเขา ... จะมีประสิทธิภาพเหมือนกัน อย่างน้อยสำหรับกรณีปกติ / ธรรมดา (เช่นไม่ใช่คนที่เข้าร่วม 14 ตาราง) ฉันค่อนข้างแน่ใจว่ามันเหมือนกัน;)
TomTom

1

ใน postgresql จะเหมือนกัน เรารู้เรื่องนี้เพราะถ้าคุณทำexplain analyzeในแต่ละคำถามแผนจะออกมาเหมือนกัน ใช้ตัวอย่างนี้:

# 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)

ทั้งสองมีต้นทุนขั้นต่ำและสูงสุดเท่ากันรวมทั้งแผนการสืบค้นเดียวกัน นอกจากนี้โปรดสังเกตว่าแม้ในข้อความค้นหาอันดับต้น ๆ team_score_2 ยังถูกนำไปใช้เป็น "ตัวกรอง"


0

ไม่น่าเป็นไปได้จริง ๆ ที่ตำแหน่งของการเข้าร่วมนี้จะเป็นปัจจัยในการตัดสินประสิทธิภาพ ฉันไม่คุ้นเคยกับการวางแผนการดำเนินการสำหรับ tsql เป็นอย่างดี แต่มีแนวโน้มว่าพวกเขาจะได้รับการปรับให้เหมาะสมกับแผนการที่คล้ายกันโดยอัตโนมัติ


0

กฎ # 0: เรียกใช้เกณฑ์มาตรฐานและดู! วิธีเดียวที่จะบอกได้ว่าจะเร็วกว่าคือลองใช้ เกณฑ์มาตรฐานประเภทนี้ทำได้ง่ายมากโดยใช้ SQL profiler

นอกจากนี้ตรวจสอบแผนการดำเนินการสำหรับแบบสอบถามที่เขียนด้วย JOIN และด้วยคำสั่ง WHERE เพื่อดูว่ามีความแตกต่างกันอย่างไร

สุดท้ายตามที่คนอื่น ๆ กล่าวไว้ทั้งสองควรได้รับการปฏิบัติเหมือนกันโดยเครื่องมือเพิ่มประสิทธิภาพที่เหมาะสมรวมถึงตัวที่ติดตั้งไว้ใน SQL Server


แต่สำหรับการรวมภายในเท่านั้น ชุดผลลัพธ์จะแตกต่างกันมากสำหรับการรวมออก
HLGEM

แน่นอน. โชคดีที่ตัวอย่างที่ให้มานี้ใช้การรวมภายใน
3Dave

1
น่าเสียดายที่คำถามเกี่ยวกับการรวมไม่ใช่การรวมภายใน
พอล

ใช่เดวิดคำถามเกี่ยวกับการเข้าร่วม ตัวอย่างที่สนับสนุนคำถามใช้การรวมภายใน
Paul

0

เร็วกว่ามั้ย? ลองใช้งานดู

อ่านเรื่องไหนง่ายกว่ากัน? สิ่งแรกสำหรับฉันดู "ถูกต้อง" มากขึ้นเนื่องจากเงื่อนไขที่ย้ายไม่เกี่ยวข้องกับการเข้าร่วมจริงๆ


0

ฉันเดาว่าอย่างแรกเพราะทำให้ตัวกรองข้อมูลเฉพาะเจาะจงมากขึ้น แต่คุณควรเห็นแผนการดำเนินการเช่นเดียวกับการเพิ่มประสิทธิภาพใด ๆ เนื่องจากอาจแตกต่างกันมากโดยขึ้นอยู่กับขนาดของข้อมูลฮาร์ดแวร์เซิร์ฟเวอร์ ฯลฯ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.