คุณสามารถอธิบายแผนการดำเนินการนี้ได้หรือไม่


20

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

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID('t') AND type in (N'U'))
DROP TABLE t
GO

CREATE TABLE t 
(
 c1 int IDENTITY(1,1) NOT NULL 
,c2 int NULL
) 
GO

insert into t
select top 1000000 a from
(select t1.number*2048 + t2.number a, newid() b
from [master]..spt_values t1 
cross join  [master]..spt_values t2
where t1.[type] = 'P' and t2.[type] = 'P') a
order by b
GO

update t set c2 = null
where c2 < 2048 * 2048 / 10
GO


CREATE CLUSTERED INDEX pk ON [t] (c1)
GO

CREATE NONCLUSTERED INDEX i ON t (c2)
GO

ตอนนี้ด้วยข้อมูลนี้ฉันได้เรียกใช้แบบสอบถามต่อไปนี้:

select * 
from t 
where 
      c2 < 1048576 
   or c2 is null
;

สร้างความประหลาดใจที่ดีของฉัน, แผนปฏิบัติการที่สร้างขึ้นสำหรับการค้นหานี้เป็นนี้ (ขออภัยสำหรับลิงก์ภายนอกขนาดใหญ่เกินไปที่จะใส่ที่นี่)

ใครสามารถอธิบายให้ฉันรู้ว่าเกิดอะไรขึ้นกับ "ค่าสแกนอย่างต่อเนื่อง " และ " คำนวณค่าสเกล " เกิดอะไรขึ้น?

วางแผน

  |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1010], [Expr1011], [Expr1012]))
       |--Merge Interval
       |    |--Sort(TOP 2, ORDER BY:([Expr1013] DESC, [Expr1014] ASC, [Expr1010] ASC, [Expr1015] DESC))
       |         |--Compute Scalar(DEFINE:([Expr1013]=((4)&[Expr1012]) = (4) AND NULL = [Expr1010], [Expr1014]=(4)&[Expr1012], [Expr1015]=(16)&[Expr1012]))
       |              |--Concatenation
       |                   |--Compute Scalar(DEFINE:([Expr1005]=NULL, [Expr1006]=NULL, [Expr1004]=(60)))
       |                   |    |--Constant Scan
       |                   |--Compute Scalar(DEFINE:([Expr1008]=NULL, [Expr1009]=(1048576), [Expr1007]=(10)))
       |                        |--Constant Scan
       |--Index Seek(OBJECT:([t].[i]), SEEK:([t].[c2] > [Expr1010] AND [t].[c2] < [Expr1011]) ORDERED FORWARD)

คำตอบ:


29

การสแกนค่าคงที่แต่ละครั้งจะสร้างแถวในหน่วยความจำเดียวโดยไม่มีคอลัมน์ สเกลาร์คำนวณด้านบนให้ผลลัพธ์แถวเดียวกับ 3 คอลัมน์

Expr1005    Expr1006    Expr1004
----------- ----------- -----------
NULL        NULL        60

สเกลาร์คำนวณด้านล่างให้ผลลัพธ์แถวเดียวกับ 3 คอลัมน์

Expr1008    Expr1009    Expr1007
----------- ----------- -----------
NULL        1048576        10

ตัวดำเนินการเรียงต่อกันสหภาพแรงงาน 2 แถวเหล่านี้เข้าด้วยกันและส่งออก 3 คอลัมน์ แต่ตอนนี้จะเปลี่ยนชื่อ

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

Expr1012คอลัมน์คือชุดของธงใช้ภายในเพื่อกำหนดบางแสวงหาคุณสมบัติสำหรับการเก็บรักษาเครื่องยนต์

สเกลาร์คำนวณถัดไปพร้อมเอาต์พุต 2 แถว

Expr1010    Expr1011    Expr1012    Expr1013    Expr1014    Expr1015
----------- ----------- ----------- ----------- ----------- -----------
NULL        NULL        60          True        4           16            
NULL        1048576     10          False       0           0      

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

[Expr1013] = Scalar Operator(((4)&[Expr1012]) = (4) AND NULL = [Expr1010]), 
[Expr1014] = Scalar Operator((4)&[Expr1012]), 
[Expr1015] = Scalar Operator((16)&[Expr1012])

Expr1014และExpr1015เพียงแค่ทดสอบว่าบิตบางอย่างอยู่ในธง Expr1013ดูเหมือนจะกลับคอลัมน์บูลจริงถ้าทั้งบิตสำหรับการ4อยู่และเป็นExpr1010NULL

จากการลองใช้ตัวดำเนินการเปรียบเทียบอื่น ๆ ในแบบสอบถามฉันได้รับผลลัพธ์เหล่านี้

+----------+----------+----------+-------------+----+----+---+---+---+---+
| Operator | Expr1010 | Expr1011 | Flags (Dec) |       Flags (Bin)       |
|          |          |          |             | 32 | 16 | 8 | 4 | 2 | 1 |
+----------+----------+----------+-------------+----+----+---+---+---+---+
| >        | 1048576  | NULL     |           6 |  0 |  0 | 0 | 1 | 1 | 0 |
| >=       | 1048576  | NULL     |          22 |  0 |  1 | 0 | 1 | 1 | 0 |
| <=       | NULL     | 1048576  |          42 |  1 |  0 | 1 | 0 | 1 | 0 |
| <        | NULL     | 1048576  |          10 |  0 |  0 | 1 | 0 | 1 | 0 |
| =        | 1048576  | 1048576  |          62 |  1 |  1 | 1 | 1 | 1 | 0 |
| IS NULL  | NULL     | NULL     |          60 |  1 |  1 | 1 | 1 | 0 | 0 |
+----------+----------+----------+-------------+----+----+---+---+---+---+

จากที่ฉันอนุมานว่าบิต 4 หมายถึง "มีจุดเริ่มต้นของช่วง" (ตรงข้ามกับการถูก จำกัด ) และบิต 16 หมายถึงจุดเริ่มต้นของช่วงนั้นรวมอยู่ด้วย

ชุดนี้ผล 6 คอลัมน์ถูกปล่อยออกมาจากผู้ประกอบการเรียงตามSORT Expr1013 DESC, Expr1014 ASC, Expr1010 ASC, Expr1015 DESCสมมติว่าTrueเป็นตัวแทนจาก1และFalseโดย0resultset ตัวแทนก่อนหน้านี้ที่มีอยู่แล้วในลำดับที่

ตามสมมติฐานก่อนหน้านี้ผลกระทบสุทธิของการจัดเรียงนี้คือการนำเสนอช่วงไปยังช่วงเวลาผสานในลำดับต่อไปนี้

 ORDER BY 
          HasStartOfRangeAndItIsNullFirst,
          HasUnboundedStartOfRangeFirst,
          StartOfRange,
          StartOfRangeIsInclusiveFirst

ตัวดำเนินการช่วงเวลาผสานเอาท์พุท 2 แถว

Expr1010    Expr1011    Expr1012
----------- ----------- -----------
NULL        NULL        60
NULL        1048576     10

สำหรับแต่ละแถวที่ปล่อยออกมาจะทำการค้นหาช่วง

Seek Keys[1]: Start:[dbo].[t].c2 > Scalar Operator([Expr1010]), 
               End: [dbo].[t].c2 < Scalar Operator([Expr1011])

ดังนั้นมันจะปรากฏขึ้นราวกับว่ามีการค้นหาสองครั้ง หนึ่งที่เห็นได้ชัดและเป็นหนึ่งใน> NULL AND < NULL > NULL AND < 1048576อย่างไรก็ตามค่าสถานะที่ผ่านเข้ามาจะปรากฏขึ้นเพื่อแก้ไขสิ่งนี้IS NULLและ< 1048576ตามลำดับ หวังว่า@sqlkiwiสามารถชี้แจงเรื่องนี้และแก้ไขความไม่ถูกต้องใด ๆ !

หากคุณเปลี่ยนแบบสอบถามเล็กน้อยเป็น

select *
from t 
where 
      c2 > 1048576 
   or c2 = 0
;

จากนั้นแผนจะดูง่ายขึ้นมากด้วยการค้นหาดัชนีด้วยการค้นหาหลายภาค

แผนแสดงให้เห็น Seek Keys

Start: c2 >= 0, End: c2 <= 0, 
Start: c2 > 1048576

คำอธิบายว่าทำไมแผนง่ายนี้จะไม่สามารถนำมาใช้สำหรับในกรณี OP ที่จะได้รับจาก SQLKiwi ในความคิดเห็นไปยังบล็อกโพสต์ก่อนหน้านี้มีการเชื่อมโยง

ดัชนีที่ค้นหาด้วยเพรดิเคตหลายตัวไม่สามารถผสมเพรดิเคตการเปรียบเทียบประเภทต่าง ๆ ได้ (เช่นIsและEqในกรณีใน OP) นี่เป็นเพียงข้อ จำกัด ในปัจจุบันของผลิตภัณฑ์ (และน่าจะเป็นเหตุผลที่ว่าทำไมการทดสอบความเท่าเทียมกันในการสอบถามที่ผ่านมาc2 = 0จะดำเนินการใช้>=และมากกว่าแค่ความเท่าเทียมกันตรงไปตรงมาแสวงหาคุณจะได้รับสำหรับการค้นหา<=c2 = 0 OR c2 = 1048576


ฉันไม่เห็นอะไรเลยในบทความของ Paul ที่อธิบายถึงความแตกต่างในธงสำหรับ [Expr1012] คุณสามารถอนุมานว่า 60/10 หมายถึงอะไรที่นี่?
มาร์คสโตร์สมิ ธ

@ MarkStorey-Smith - เขาบอกว่า62สำหรับการเปรียบเทียบความเท่าเทียมกัน ผมคิดว่า60ต้องหมายถึงว่าแทนที่จะ> AND < ดังแสดงในแผนการที่คุณในความเป็นจริงได้รับ>= AND <=ยกเว้นว่าจะชัดเจนIS NULLธงอาจจะ (?) หรืออาจจะบิต2แสดงให้เห็นอย่างอื่นที่ไม่เกี่ยวข้องกันและ60ยังคงมีความเท่าเทียมกันเมื่อฉันทำset ansi_nulls offและเปลี่ยนเป็นc2 = nullก็ยังคงเข้าพักที่60
Martin Smith

2
@MartinSmith 60 แน่นอนสำหรับการเปรียบเทียบกับ NULL นิพจน์ขอบเขตของขอบเขตใช้ค่า NULL เพื่อแสดง 'ไม่ จำกัด ' ที่ปลายทั้งสอง การค้นหานั้นพิเศษเสมอเช่นการค้นหาเริ่มต้น:> & จบ: <Expr แทนที่จะรวมการใช้> = และ <= ขอบคุณสำหรับความคิดเห็นบล็อกฉันจะโพสต์คำตอบหรือแสดงความคิดเห็นอีกต่อไปในการตอบกลับในตอนเช้า (สายเกินไปที่จะทำเพื่อความยุติธรรมในตอนนี้)
พอลไวท์พูดว่า GoFundMonica

@SQLKiwi - ขอบคุณ นั่นทำให้รู้สึก หวังว่าฉันจะหาบิตที่หายไปบางส่วนก่อนหน้านั้น
Martin Smith

ขอบคุณมากฉันยังคงสนใจสิ่งนี้อยู่ แต่ดูเหมือนจะอธิบายสิ่งต่าง ๆ ได้ดีคำถามหลักที่เหลืออยู่คือคำถามที่คุณถาม @SQLKiwi บนบล็อกของเขา ฉันจะใคร่ครวญคำตอบของคุณอีกสองสามวันเพื่อให้แน่ใจว่าฉันไม่มีคำถามติดตามและฉันจะยอมรับคำตอบของคุณ ขอบคุณอีกครั้งมันช่วยได้มาก
Andrew Savinykh

13

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

ตัวดำเนินการ Compute Scalar กำลังโหลดด้วยค่า NULL และค่า 1045876 ดังนั้นพวกเขาจึงจะใช้กับการเข้าร่วมแบบวนรอบอย่างชัดเจนในความพยายามกรองข้อมูล

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

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

เพิ่ม: ประโยคสุดท้ายนั้นปิด มีอยู่สองแสวงหา ฉันอ่านผิดแผน แนวคิดที่เหลืออยู่เหมือนกันและเป้าหมายคือการผ่านขั้นต่ำจะเหมือนกัน

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