SQL Server เลือกผลลัพธ์ที่คาดเดาไม่ได้ (ข้อผิดพลาด dbms?)


37

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

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

มันทำงานได้ตามที่คาดหวังใน SQL Server 2000 (ส่งคืน 12 แถว) แต่ในปี 2008 และ 2012 จะส่งคืนเพียงแถวเดียว

create table dual (dummy int)

insert into dual values (0)

create table men (
man_id int,
wife_id int )

-- there are 12 men, 6 married 
insert into men values (1, 1)
insert into men values (2, 2)
insert into men values (3, null)
insert into men values (4, null)
insert into men values (5, null)
insert into men values (6, 3)
insert into men values (7, 5)
insert into men values (8, 7)
insert into men values (9, null)
insert into men values (10, null)
insert into men values (11, null)
insert into men values (12, 9)

สิ่งนี้จะส่งคืนแถวเดียว: 1 1 2

select 
man_id,
wife_id,
(select count( * ) from 
    (select dummy from dual
     union select men.wife_id  ) family_members
) as family_size
from men
--where wife_id = 2 -- uncomment me and try again

บรรทัดสุดท้ายที่ไม่ใส่เครื่องหมายข้อคิดเห็นและให้: 2 2 2

มีพฤติกรรมแปลก ๆ มากมาย:

  • หลังจากชุดของหยดสร้างตัดและแทรกในตาราง "ผู้ชาย" บางครั้งก็ใช้งานได้ (คืน 12 แถว)
  • เมื่อคุณเปลี่ยน "union select men.wife_id" เป็น "union all select men.wife_id" หรือ "union select isnull (men.wife_id, null)" (!!!) มันจะคืนค่า 12แถว (ตามที่คาดไว้)
  • พฤติกรรมแปลก ๆ ดูเหมือนจะไม่เกี่ยวข้องกับประเภทข้อมูลของคอลัมน์ "wife_id" เราสังเกตมันในระบบการพัฒนาที่มีชุดข้อมูลที่มากขึ้น
  • "where wife_id> 0" ส่งคืน 6 แถว
  • เรายังสังเกตเห็นพฤติกรรมที่แปลกประหลาดของมุมมองด้วยข้อความประเภทนี้ SELECT * ส่งคืนชุดย่อยของแถว SELECT TOP 1000 คืนค่าทั้งหมด

คำตอบ:


35

เรากำลังทำอะไรผิดพลาดหรือเป็นข้อผิดพลาดของ SQL Server หรือไม่

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

ข้อผิดพลาดต้องมีสามส่วนผสม:

  1. ลูปซ้อนพร้อมการอ้างอิงภายนอก (ใช้)
  2. สปูลดัชนีขี้เกียจด้านในที่ค้นหาข้อมูลอ้างอิงด้านนอก
  3. ตัวดำเนินการต่อข้อมูลภายใน

ตัวอย่างเช่นแบบสอบถามในคำถามสร้างแผนดังต่อไปนี้:

แผนข้อเขียน

มีหลายวิธีในการลบหนึ่งในองค์ประกอบเหล่านี้ดังนั้นข้อผิดพลาดจะไม่ทำซ้ำ

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

รายละเอียดเพิ่มเติม

Spool ดัชนีขี้เกียจเก็บแถวผลด้านในอย่างเกียจคร้านในตารางงานที่ทำดัชนีโดยการอ้างอิงภายนอก (พารามิเตอร์ที่สัมพันธ์กัน) ค่า หาก Lazy Index Spool ถูกขอให้อ้างอิงภายนอกที่เคยเห็นมาก่อนจะดึงแถวผลลัพธ์ที่แคชไว้จากตารางงาน ("ย้อนกลับ") หากสปูลถูกถามถึงค่าการอ้างอิงภายนอกที่ไม่เคยเห็นมาก่อนมันจะรันทรีย่อยด้วยค่าการอ้างอิงภายนอกปัจจุบันและแคชผลลัพธ์ ("rebind") เพรดิเคตค้นหาบน Lazy Index Spool ระบุคีย์สำหรับตารางงาน

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

CParamBounds: FNeedToReload

เมื่อทรีย่อยที่แสดงด้านบนมีอยู่โดยเฉพาะเมื่อมีการใช้การเรียงต่อกันเกิดข้อผิดพลาด (อาจเป็นปัญหา ByVal / ByRef / Copy) ที่มีการเชื่อมโยงซึ่งCParamBounds:FNeedToReloadจะส่งกลับค่าเท็จเสมอโดยไม่คำนึงว่าการอ้างอิงภายนอกเปลี่ยนแปลงจริงหรือไม่

เมื่อมีทรีย่อยเดียวกัน แต่ใช้ Merge Union หรือ Hash Union คุณสมบัติที่สำคัญนี้จะถูกตั้งค่าอย่างถูกต้องในการวนซ้ำแต่ละครั้งและ Lazy Index Spool ย้อนกลับหรือ rebinds แต่ละครั้งตามความเหมาะสม Distinct Sort and Stream Aggregate นั้นไร้ที่ติเลยทีเดียว ความสงสัยของฉันคือ Merge and Hash Union ทำสำเนาของค่าก่อนหน้าในขณะที่การต่อข้อมูลใช้การอ้างอิง มันเป็นไปไม่ได้เลยที่จะตรวจสอบเรื่องนี้โดยที่ไม่สามารถเข้าถึงซอร์สโค้ดเซิร์ฟเวอร์ SQL ได้

ผลลัพธ์สุทธิคือ Lazy Index Spool ในรูปแบบแผนปัญหามักคิดว่าได้เห็นการอ้างอิงภายนอกปัจจุบันแล้วย้อนกลับโดยค้นหาในตารางการทำงานโดยทั่วไปไม่พบสิ่งใดดังนั้นจึงไม่มีการส่งคืนแถวสำหรับการอ้างอิงภายนอกนั้น ผู้ใช้สปูลจะดำเนินการตามRewindHelperวิธีการของมันในการดีบักผ่านการดำเนินการและไม่เคยReloadHelperใช้วิธีการ (reload = rebind ในบริบทนี้) สิ่งนี้ชัดเจนในแผนการดำเนินการเนื่องจากตัวดำเนินการภายใต้สปูลทั้งหมดมี 'Number of Executions = 1'

RewindHelper

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

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

การสาธิต

ตารางและข้อมูลตัวอย่าง:

CREATE TABLE #T1 
(
    pk integer IDENTITY NOT NULL,
    c1 integer NOT NULL,

    CONSTRAINT PK_T1
    PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6);

แบบสอบถาม (เล็กน้อย) ต่อไปนี้สร้างการนับที่ถูกต้องสองสำหรับแต่ละแถว (รวม 18 รายการ) โดยใช้ Merge Union

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C;

ผสานแผนสหภาพ

หากเราเพิ่มคำใบ้เพื่อบังคับให้มีการต่อข้อมูล:

SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY 
(
    SELECT COUNT_BIG(*) AS c1
    FROM
    (
        SELECT T1.c1
        UNION
        SELECT NULL
    ) AS U
) AS C
OPTION (CONCAT UNION);

แผนการดำเนินการมีรูปร่างที่เป็นปัญหา:

แผนตัดแบ่ง

และผลลัพธ์ไม่ถูกต้องเพียงสามแถว:

ผลสามแถว

แม้ว่าจะไม่รับประกันพฤติกรรมนี้ แต่แถวแรกจากการสแกนดัชนีแบบคลัสเตอร์มีc1ค่าเท่ากับ 1 มีอีกสองแถวที่มีค่านี้ดังนั้นจึงมีการสร้างแถวทั้งหมดสามแถว

ตอนนี้ตัดทอนตารางข้อมูลและโหลดด้วยแถวที่ซ้ำซ้อนของแถว 'แรก':

TRUNCATE TABLE #T1;

INSERT #T1 (c1)
VALUES
    (1), (2), (3), (4), (5), (6),
    (1), (2), (3), (4), (5), (6),
    (1), (1), (1), (1), (1), (1);

ตอนนี้แผนการเชื่อมต่อคือ:

แผนเชื่อมต่อ 8 แถว

และตามที่ระบุไว้มีการสร้างแถว 8 แถวซึ่งทั้งหมดนี้มีc1 = 1แน่นอน:

ผลลัพธ์ 8 แถว

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


ข้อผิดพลาดของผลลัพธ์ที่ผิดพลาดนี้ได้รับการแก้ไขในบางช่วง มันไม่ทำซ้ำสำหรับฉันใน SQL Server รุ่นใด ๆ ตั้งแต่ปี 2555 เป็นต้นไป มันทำซ้ำในการสร้าง SQL Server 2008 R2 SP3-GDR 10.50.6560.0 (X64)


-3

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

select 
m1.man_id,
m1.wife_id,
(select count( * ) from 
    (select dummy from dual
     union
     select m2.wife_id
     from men m2
     where m2.man_id = m1.man_id) family_members
) as family_size
from men m1

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