ทำไมจึงเร็วกว่านี้และปลอดภัยที่จะใช้หรือไม่ (ที่ตัวอักษรตัวแรกอยู่ที่ไหนในตัวอักษร)


10

เรื่องย่อสั้น ๆ เรากำลังอัปเดตผู้คนกลุ่มเล็ก ๆ ด้วยค่านิยมจากกลุ่มคนที่มีขนาดใหญ่มาก ในการทดสอบล่าสุดการอัพเดทนี้ใช้เวลาประมาณ 5 นาทีในการรัน

เราพบสิ่งที่ดูเหมือนว่าการปรับให้เหมาะสมที่สุดที่เป็นไปได้ซึ่งดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์แบบ! แบบสอบถามเดียวกันนี้ทำงานในเวลาน้อยกว่า 2 นาทีและสร้างผลลัพธ์เดียวกันอย่างสมบูรณ์แบบ

นี่คือแบบสอบถาม บรรทัดสุดท้ายถูกเพิ่มเป็น "การเพิ่มประสิทธิภาพ" ทำไมเวลาค้นหาที่ลดลงอย่างมาก พวกเราขาดอะไรบางอย่าง? สิ่งนี้นำไปสู่ปัญหาในอนาคตได้หรือไม่?

UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
    AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')

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

แผนข้อความค้นหา (ปกติ): https://www.brentozar.com/pastetheplan/?id=rypV84y7V
แผนข้อความค้นหา (ด้วย "การเพิ่มประสิทธิภาพ"): https://www.brentozar.com/pastetheplan/?id=r1aC2my7E


4
ตอบกลับไปยังบันทึกย่อด้านเทคนิคของคุณ: AND LEFT(TRIM(largeTbl.last_name), 1) BETWEEN 'a' AND 'z' COLLATE LATIN1_GENERAL_CI_AIควรทำในสิ่งที่คุณต้องการโดยไม่ต้องให้คุณแสดงรายการอักขระทั้งหมดและมีรหัสที่อ่านยาก
Erik A

คุณมีแถวที่สภาพสุดท้ายในWHEREเป็นเท็จ? โดยเฉพาะอย่างยิ่งโปรดทราบว่าการเปรียบเทียบอาจคำนึงถึงขนาดตัวพิมพ์
jpmc26

@ErikvonAsmuth ทำให้เป็นจุดที่ยอดเยี่ยม แต่มีข้อควรทราบทางเทคนิคเล็กน้อย: สำหรับ SQL Server 2008 และ 2008 R2 จะเป็นการดีที่สุดที่จะใช้การเปรียบเทียบรุ่น "100" (หากมีให้สำหรับการใช้งานวัฒนธรรม / โลแคล) Latin1_General_100_CI_AIเพื่อที่จะเป็น และสำหรับ SQL Server 2012 และใหม่กว่า (อย่างน้อย SQL Server 2019) จะเป็นการดีที่สุดที่จะใช้ Collation ที่เปิดใช้งานอักขระเพิ่มเติมในเวอร์ชันสูงสุดสำหรับโลแคลที่ใช้ ดังนั้นจะเป็นLatin1_General_100_CI_AI_SCในกรณีนี้ เวอร์ชั่น> 100 (จนถึงภาษาญี่ปุ่นเท่านั้น) ไม่มี (หรือต้องการ) _SC(เช่นJapanese_XJIS_140_CI_AI)
โซโลมอน Rutzky

คำตอบ:


9

ขึ้นอยู่กับข้อมูลในตารางดัชนีของคุณ .... ยากที่จะพูดโดยไม่สามารถเปรียบเทียบแผนการดำเนินการ / สถิติเวลา + io

ความแตกต่างที่ฉันคาดหวังคือการกรองพิเศษเกิดขึ้นก่อนเข้าร่วมระหว่างสองตาราง ในตัวอย่างของฉันฉันเปลี่ยนการปรับปรุงเพื่อเลือกเพื่อใช้ตารางของฉันซ้ำ

แผนการดำเนินการด้วย "การเพิ่มประสิทธิภาพ" ป้อนคำอธิบายรูปภาพที่นี่

แผนปฏิบัติการ

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

แผนการดำเนินการโดยไม่มี "การเพิ่มประสิทธิภาพ" ป้อนคำอธิบายรูปภาพที่นี่

แผนปฏิบัติการ

ตัวกรองหายไปซึ่งหมายความว่าเราจะต้องพึ่งพาการเข้าร่วมเพื่อกรองระเบียนที่ไม่จำเป็น

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

แก้ไข:

ความชัดเจนหลังจากได้รับแผนแบบสอบถามสองแบบ:

ข้อความค้นหากำลังอ่าน 550M Rows จากตารางขนาดใหญ่และกรองออก ป้อนคำอธิบายรูปภาพที่นี่

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

การทำให้เซิร์ฟเวอร์ sql ใช้ดัชนีอื่น (แผนแบบสอบถาม) / การเพิ่มดัชนีสามารถแก้ไขปัญหานี้ได้

เหตุใดข้อความค้นหาการเพิ่มประสิทธิภาพจึงไม่มีปัญหาเดียวกันนี้

เนื่องจากมีการใช้แผนคิวรีที่แตกต่างกันโดยมีการสแกนแทนการค้นหา

ป้อนคำอธิบายรูปภาพที่นี่ ป้อนคำอธิบายรูปภาพที่นี่

โดยไม่ทำการค้นหาใด ๆ แต่ส่งคืนแถว 4M เท่านั้นที่จะทำงานได้

ความแตกต่างถัดไป

การไม่คำนึงถึงความแตกต่างของการอัปเดต (ไม่มีการอัปเดตในเคียวรีที่ปรับให้เหมาะสม) การจับคู่แบบแฮชจะใช้กับเคียวรีที่ปรับให้เหมาะสม:

ป้อนคำอธิบายรูปภาพที่นี่

แทนที่จะเข้าร่วมลูปซ้อนกันบนที่ไม่เหมาะสม:

ป้อนคำอธิบายรูปภาพที่นี่

การวนซ้ำซ้อนกันจะดีที่สุดเมื่อตารางหนึ่งมีขนาดเล็กและอีกอันหนึ่งใหญ่ เนื่องจากทั้งคู่มีขนาดใกล้เคียงกันฉันจึงขอยืนยันว่าการจับคู่แฮชเป็นตัวเลือกที่ดีกว่าในกรณีนี้

ภาพรวม

แบบสอบถามที่ปรับให้เหมาะสม ป้อนคำอธิบายรูปภาพที่นี่

แผนของแบบสอบถามที่ปรับให้เหมาะสมนั้นมีความคล้ายคลึงกันใช้การเข้าร่วมแฮชจับคู่และต้องการกรอง IO ที่เหลือน้อยลง นอกจากนี้ยังใช้บิตแมปเพื่อกำจัดค่าคีย์ที่ไม่สามารถสร้างแถวเข้าร่วมได้ (ยังไม่มีการอัพเดทอะไร)

แบบสอบถาม ป้อนคำอธิบายรูปภาพที่นี่ ที่ไม่ปรับให้เหมาะสมแผนของแบบสอบถามที่ไม่ได้รับการเพิ่มประสิทธิภาพนั้นไม่มีความคล้ายคลึงกันใช้การเข้าร่วมลูปซ้อนกันและจำเป็นต้องทำการกรอง IO ที่เหลือในบันทึก 550M (เช่นการอัปเดตที่เกิดขึ้น)

คุณสามารถทำอะไรเพื่อปรับปรุงการค้นหาที่ไม่ปรับให้เหมาะสม

  • การเปลี่ยนดัชนีให้มี first_name & last_name ในรายการคอลัมน์คีย์:

    สร้างดัชนี IX_largeTableOfPeople_birth_date_first_name_last_name บน dbo.largeTableOfPeople (วันเกิด, ชื่อ _, นามสกุล _ ชื่อ) รวม (id)

แต่เนื่องจากการใช้ฟังก์ชั่นและตารางนี้มีขนาดใหญ่จึงอาจไม่ใช่ทางออกที่ดีที่สุด

  • การอัปเดตสถิติโดยใช้การคอมไพล์ใหม่เพื่อลองและรับแผนที่ดีกว่า
  • การเพิ่ม OPTION (HASH JOIN, MERGE JOIN)ให้กับแบบสอบถาม
  • ...

ทดสอบข้อมูล + คำค้นหาที่ใช้

CREATE TABLE #smallTableOfPeople(importantValue int, birthDate datetime2, first_name varchar(50),last_name varchar(50));
CREATE TABLE #largeTableOfPeople(importantValue int, birth_date datetime2, first_name varchar(50),last_name varchar(50));


set nocount on;
DECLARE @i int = 1
WHILE @i <= 1000
BEGIN
insert into #smallTableOfPeople (importantValue,birthDate,first_name,last_name)
VALUES(NULL, dateadd(mi,@i,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @i += 1;
END


set nocount on;
DECLARE @j int = 1
WHILE @j <= 20000
BEGIN
insert into #largeTableOfPeople (importantValue,birth_Date,first_name,last_name)
VALUES(@j, dateadd(mi,@j,'2018-01-18 11:05:29.067'),'Frodo','Baggins');

set @j += 1;
END


SET STATISTICS IO, TIME ON;

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å');

SELECT  smallTbl.importantValue , largeTbl.importantValue
FROM #smallTableOfPeople smallTbl
JOIN #largeTableOfPeople largeTbl
    ON largeTbl.birth_date = smallTbl.birthDate
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.last_name)),RTRIM(LTRIM(largeTbl.last_name))) = 4
    AND DIFFERENCE(RTRIM(LTRIM(smallTbl.first_name)),RTRIM(LTRIM(largeTbl.first_name))) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
--AND LEFT(RTRIM(LTRIM(largeTbl.last_name)), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')




drop table #largeTableOfPeople;
drop table #smallTableOfPeople;

8

ไม่ชัดเจนว่าจริง ๆ แล้วแบบสอบถามที่สองเป็นการปรับปรุง

แผนการดำเนินการประกอบด้วย QueryTimeStats ที่แสดงความแตกต่างอย่างมากน้อยกว่าที่ระบุไว้ในคำถาม

แผนช้ามีเวลาที่ผ่านไป257,556 ms(4 นาที 17 วินาที) แผนเร็วมีเวลาที่ผ่านไปเป็นเวลา190,992 ms(3 นาที 11 วินาที) แม้จะมีระดับความเท่าเทียม 3

ยิ่งไปกว่านั้นแผนการที่สองกำลังทำงานอยู่ในฐานข้อมูลที่ไม่มีงานต้องทำหลังจากการเข้าร่วม

แผนแรก

ป้อนคำอธิบายรูปภาพที่นี่

แผนสอง

ป้อนคำอธิบายรูปภาพที่นี่

เพื่อให้สามารถอธิบายเวลาพิเศษได้โดยงานที่จำเป็นในการอัปเดต 3.5 ล้านแถว (งานที่ต้องใช้ในผู้ดำเนินการอัปเดตเพื่อค้นหาแถวเหล่านี้สลักหน้าเขียนการอัปเดตไปยังหน้าและบันทึกธุรกรรมไม่สำคัญ)

หากนี่เป็นความจริงที่ทำซ้ำได้เมื่อเปรียบเทียบกับกับคำอธิบายนั้นคือคุณเพิ่งโชคดีในกรณีนี้

ตัวกรองที่มีINเงื่อนไข37 รายการตัดออกเพียง 51 แถวจาก 4,008,334 ในตาราง แต่ตัวเพิ่มประสิทธิภาพพิจารณาว่าจะกำจัดได้มากกว่า

ป้อนคำอธิบายรูปภาพที่นี่

   LEFT(TRIM(largeTbl.last_name), 1) IN ( 'a', 'à', 'á', 'b',
                                          'c', 'd', 'e', 'è',
                                          'é', 'f', 'g', 'h',
                                          'i', 'j', 'k', 'l',
                                          'm', 'n', 'o', 'ô',
                                          'ö', 'p', 'q', 'r',
                                          's', 't', 'u', 'ü',
                                          'v', 'w', 'x', 'y',
                                          'z', 'æ', 'ä', 'ø', 'å' ) 

การประมาณค่า cardinality ที่ไม่ถูกต้องนั้นมักจะเป็นสิ่งที่ไม่ดี ในกรณีนี้มันสร้างแผน (และขนาน) ที่มีรูปร่างแตกต่างกันซึ่งเห็นได้ชัดว่า (?) ทำงานได้ดีขึ้นสำหรับคุณ

หากไม่มีTRIMSQL Server จะสามารถแปลงเป็นช่วงช่วงในฮิสโตแกรมคอลัมน์ฐานและให้ค่าประมาณที่แม่นยำกว่า แต่มีTRIMเพียงการคาดเดา

ธรรมชาติของการคาดเดาที่อาจแตกต่างกัน แต่ประมาณการสำหรับกริยาเดียวบนLEFT(TRIM(largeTbl.last_name), 1)มีในบางสถานการณ์*table_cardinality/estimated_number_of_distinct_column_valuesประมาณเพียงเพื่อจะ

ฉันไม่แน่ใจว่าสิ่งที่สถานการณ์ - ขนาดของข้อมูลดูเหมือนว่าจะมีส่วนร่วม ฉันสามารถทำซ้ำสิ่งนี้ด้วยประเภทข้อมูลความยาวคงที่ที่นี่แต่ได้แตกต่างสูงกว่าเดาด้วยvarchar(ซึ่งเพิ่งใช้การคาดเดา 10% แบนและประมาณ 100,000 แถว) @Solomon Rutzkyชี้ให้เห็นว่าหากvarchar(100)เบาะที่มีช่องว่างต่อท้ายเหมือนที่เกิดขึ้นสำหรับcharการประมาณการที่ต่ำกว่าจะใช้

INรายการมีการขยายออกไปORและ SQL Server ใช้backoff ชี้แจงสูงสุด 4 ภาคพิจารณา ดังนั้นการ219.707ประมาณจะมาถึงดังนี้

DECLARE @TableCardinality FLOAT = 4008334, 
        @DistinctColumnValueEstimate FLOAT = 34207

DECLARE @NotSelectivity float = 1 - (1/@DistinctColumnValueEstimate)

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