Cardinality Estimate สำหรับผู้ประกอบการ LIKE (ตัวแปรท้องถิ่น)


24

ฉันรู้สึกว่าเมื่อใช้ตัวLIKEดำเนินการในการปรับให้เหมาะสมสำหรับสถานการณ์ที่ไม่รู้จักทั้งมรดกและ CE ใหม่ใช้ประมาณการ 9% (สมมติว่ามีสถิติที่เกี่ยวข้องพร้อมใช้งานและเครื่องมือเพิ่มประสิทธิภาพการสืบค้นไม่จำเป็นต้องคาดเดาการเลือก)

เมื่อดำเนินการค้นหาด้านล่างกับฐานข้อมูลเครดิตฉันได้รับการประมาณการที่แตกต่างกันภายใต้ CE ที่แตกต่างกัน ภายใต้ CE ใหม่ฉันได้รับการประมาณ 900 แถวซึ่งฉันคาดหวังภายใต้ CE ดั้งเดิมฉันได้รับการประมาณ 241.416 และฉันไม่สามารถทราบได้ว่าการประเมินนี้มาจากอะไร มีใครสามารถที่จะหลั่งน้ำตาแสงใด ๆ ?

-- New CE (Estimate = 900)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName;

-- Forcing Legacy CE (Estimate = 241.416)
DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM [Credit].[dbo].[member]
WHERE [lastname] LIKE @LastName
OPTION (
QUERYTRACEON 9481,
QUERYTRACEON 9292,
QUERYTRACEON 9204,
QUERYTRACEON 3604
);

ในสถานการณ์ของฉันฉันได้ตั้งฐานข้อมูลเครดิตเป็นระดับความเข้ากันได้ 120 แล้วดังนั้นในการสืบค้นครั้งที่สองฉันจึงใช้แฟล็กการติดตามเพื่อบังคับให้ CE ดั้งเดิมและให้ข้อมูลเกี่ยวกับสถิติที่ใช้ / พิจารณาโดยเครื่องมือเพิ่มประสิทธิภาพการสืบค้น ฉันสามารถดูสถิติของคอลัมน์ใน 'นามสกุล' ได้ แต่ฉันก็ยังไม่สามารถอธิบายได้ว่าการประมาณ 241.416 นั้นมาจากไหน

ฉันไม่สามารถหาอะไรออนไลน์ได้นอกจากบทความ Itzik Ben-Ganซึ่งระบุว่า "เมื่อใช้คำสั่ง LIKE ในการปรับให้เหมาะสมสำหรับสถานการณ์ที่ไม่รู้จักทั้งมรดกและ CE ใหม่ใช้ประมาณการประมาณ 9 เปอร์เซ็นต์" ข้อมูลในโพสต์นั้นดูเหมือนจะไม่ถูกต้อง

คำตอบ:


28

การคาดเดาLIKE ในกรณีของคุณขึ้นอยู่กับ:

  • G: การคาดเดามาตรฐาน 9% ( sqllang!x_Selectivity_Like)
  • M: A ปัจจัย 6 (หมายเลขมายากล)
  • D: ความยาวข้อมูลเฉลี่ยเป็นไบต์ (จากสถิติ) ปัดลงเป็นจำนวนเต็ม

โดยเฉพาะการsqllang!CCardUtilSQL7::ProbLikeGuessใช้:

Selectivity (S) = G / M * LOG(D)

หมายเหตุ:

  • LOG(D)ระยะถูกละไว้ถ้าDอยู่ระหว่าง 1 และ 2
  • หากDน้อยกว่า 1 (รวมถึงการสูญหายหรือNULLสถิติ):
    D = FLOOR(0.5 * maximum column byte length)

การเล่นโวหารและความซับซ้อนเช่นนี้ค่อนข้างปกติของ CE ดั้งเดิม

ในตัวอย่างคำถามความยาวเฉลี่ยคือ 5 (5.6154 จากการDBCC SHOW_STATISTICSปัดเศษลง):

ค่าประมาณ = 10,000 * (0.09 / 6 * LOG (5)) = 241.416

ค่าตัวอย่างอื่น ๆ :

 D   = ประมาณโดยใช้สูตรสำหรับ S
 15 = 406.208
 14 = 395.859
 13 = 384.742
 12 = 372.736
 11 = 359.684
 10 = 345.388
 09 = 329.584
 08 = 311.916
 07 = 291.887
 06 = 268.764
 05 = 241.416
 04 = 207.944
 03 = 164.792
 02 = 150.000 (ไม่ได้ใช้ LOG)
 01 = 150.000 (ไม่ได้ใช้ LOG)
 00 = 291.887 (LOG 7) / * FLOOR (0.5 * 15) [15 เนื่องจากนามสกุลคือ varchar (15)] * /

อุปกรณ์ทดสอบ

DECLARE
    @CharLength integer = 5, -- Set length here
    @Counter integer = 1;

CREATE TABLE #T (c1 varchar(15) NULL);

-- Add 10,000 rows
SET NOCOUNT ON;
SET STATISTICS XML OFF;

BEGIN TRANSACTION;
WHILE @Counter <= 10000
BEGIN
    INSERT #T (c1) VALUES (REPLICATE('X', @CharLength));
    SET @Counter = @Counter + 1;
END;
COMMIT TRANSACTION;

SET NOCOUNT OFF;
SET STATISTICS XML ON;

-- Test query
DECLARE @Like varchar(15);
SELECT * FROM #T AS T 
WHERE T.c1 LIKE @Like;

DROP TABLE #T;

15

ฉันทดสอบกับ SQL Server 2014 ด้วย CE ดั้งเดิมและไม่ได้รับ 9% เนื่องจากการประเมินความสำคัญเชิงหัวใจเช่นกัน ฉันไม่พบสิ่งที่ถูกต้องทางออนไลน์ดังนั้นฉันจึงทำการทดสอบและฉันพบแบบจำลองที่เหมาะกับกรณีทดสอบทั้งหมดที่ฉันลองใช้ แต่ฉันไม่แน่ใจว่ามันเสร็จสมบูรณ์

ในรูปแบบที่ฉันพบการประมาณนั้นมาจากจำนวนแถวในตารางความยาวคีย์เฉลี่ยของสถิติสำหรับคอลัมน์ที่กรองและบางครั้งความยาวประเภทข้อมูลของคอลัมน์ที่กรอง มีสองสูตรที่แตกต่างกันที่ใช้สำหรับการประเมิน

ถ้า FLOOR (ความยาวคีย์เฉลี่ย) = 0 สูตรการประมาณจะละเว้นสถิติคอลัมน์และสร้างการประมาณตามความยาวประเภทข้อมูล ฉันทดสอบกับ VARCHAR (N) เท่านั้นดังนั้นจึงเป็นไปได้ว่ามีสูตรที่แตกต่างกันสำหรับ NVARCHAR (N) นี่คือสูตรสำหรับ VARCHAR (N):

(การประมาณแถว) = (แถวในตาราง) * (-0.004869 + 0.032649 * log10 (ความยาวของชนิดข้อมูล))

นี่เป็นแบบที่ดีมาก แต่ไม่แม่นยำอย่างสมบูรณ์:

กราฟสูตรแรก

แกน x คือความยาวของชนิดข้อมูลและแกน y คือจำนวนแถวโดยประมาณสำหรับตารางที่มี 1 ล้านแถว

เครื่องมือเพิ่มประสิทธิภาพคิวรีจะใช้สูตรนี้หากคุณไม่มีสถิติในคอลัมน์หรือหากคอลัมน์มีค่า NULL เพียงพอที่จะทำให้ความยาวของคีย์โดยเฉลี่ยต่ำกว่า 1

ตัวอย่างเช่นสมมติว่าคุณมีตารางที่มี 150,000 แถวพร้อมตัวกรองบน ​​VARCHAR (50) และไม่มีสถิติคอลัมน์ การคาดคะเนแถวคือ:

150000 * (-0.004869 + 0.032649 * log10 (50)) = 7590.1 แถว

SQL ทดสอบ:

CREATE TABLE X_CE_LIKE_TEST_1 (
STRING VARCHAR(50)
);

CREATE STATISTICS X_STAT_CE_LIKE_TEST_1 ON X_CE_LIKE_TEST_1 (STRING) WITH NORECOMPUTE;

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_1 WITH (TABLOCK) (STRING)
    SELECT TOP (150000) 'ZZZZZ'
    FROM NUMS
    ORDER BY NUM;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_1
WHERE STRING LIKE @LastName;

SQL Server ให้การนับแถวโดยประมาณ 7242.47 ซึ่งใกล้เคียงที่สุด

ถ้า FLOOR (ความยาวคีย์เฉลี่ย)> = 1 จะใช้สูตรที่แตกต่างกันซึ่งขึ้นอยู่กับค่าของ FLOOR (ความยาวคีย์เฉลี่ย) นี่คือตารางค่าบางอย่างที่ฉันได้ลอง:

1    1.5%
2    1.5%
3    1.64792%
4    2.07944%
5    2.41416%
6    2.68744%
7    2.91887%
8    3.11916%
9    3.29584%
10   3.45388%

ถ้า FLOOR (ความยาวคีย์เฉลี่ย) <6 ให้ใช้ตารางด้านบน มิฉะนั้นให้ใช้สมการต่อไปนี้:

(การประมาณแถว) = (แถวในตาราง) * (-0.003381 + 0.034539 * log10 (FLOOR (ความยาวคีย์เฉลี่ย))

อันนี้มีขนาดพอดีดีกว่าอีกอันหนึ่ง แต่ก็ยังไม่แม่นยำอย่างสมบูรณ์

กราฟสูตรที่สอง

แกน x คือความยาวคีย์เฉลี่ยและแกน y คือจำนวนแถวโดยประมาณสำหรับตารางที่มี 1 ล้านแถว

หากต้องการตัวอย่างอื่นสมมติว่าคุณมีตารางที่มี 10k แถวที่มีความยาวคีย์เฉลี่ย 5.5 สำหรับสถิติในคอลัมน์ที่กรอง การประมาณแถวจะเป็น:

10000 * 0.241416 = 241.416 แถว

SQL ทดสอบ:

CREATE TABLE X_CE_LIKE_TEST_2 (
STRING VARCHAR(50)
);

WITH
    L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
    L1 AS (SELECT 1 AS c FROM L0 A CROSS JOIN L0 B),
    L2 AS (SELECT 1 AS c FROM L1 A CROSS JOIN L1 B),
    L3 AS (SELECT 1 AS c FROM L2 A CROSS JOIN L2 B),
    L4 AS (SELECT 1 AS c FROM L3 A CROSS JOIN L3 B CROSS JOIN L2 C),
    NUMS AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NUM FROM L4)  
    INSERT INTO X_CE_LIKE_TEST_2 WITH (TABLOCK) (STRING)
    SELECT TOP (10000) 
    CASE 
      WHEN NUM % 2 = 1 THEN REPLICATE('Z', 5) 
      ELSE REPLICATE('Z', 6)
    END
    FROM NUMS
    ORDER BY NUM;

CREATE STATISTICS X_STAT_CE_LIKE_TEST_2 ON X_CE_LIKE_TEST_2 (STRING) 
WITH NORECOMPUTE, FULLSCAN;

DECLARE @LastName VARCHAR(15) = 'BA%'
SELECT * FROM X_CE_LIKE_TEST_2
WHERE STRING LIKE @LastName;

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

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

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