SQL Server แบ่ง A <> B เป็น A <B หรือ A> B โดยให้ผลลัพธ์ที่แปลกประหลาดหาก B ไม่ได้กำหนดไว้ล่วงหน้า


26

เราพบปัญหาที่น่าสนใจกับ SQL Server พิจารณาตัวอย่างการทำซ้ำต่อไปนี้:

CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');

SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
  AND s_guid <> NEWID();

DROP TABLE #test;

ซอ

โปรดลืมสักครู่ว่าs_guid <> NEWID()สภาพดูเหมือนจะไร้ประโยชน์โดยสิ้นเชิง - นี่เป็นเพียงตัวอย่างที่ไม่สำคัญ เนื่องจากความน่าจะเป็นของการNEWID()จับคู่ค่าคงที่ที่กำหนดมีค่าน้อยมากจึงควรประเมินเป็น TRUE ทุกครั้ง

แต่มันก็ไม่ได้ การเรียกใช้คิวรีนี้มักจะส่งคืน 1 แถว แต่บางครั้ง (ค่อนข้างบ่อยมากกว่า 1 ครั้งจาก 10) จะคืนค่า 0 แถว ฉันได้ทำซ้ำกับ SQL Server 2008 ในระบบของฉันและคุณสามารถทำซ้ำออนไลน์กับซอที่เชื่อมโยงด้านบน (SQL Server 2014)

เมื่อดูที่แผนการดำเนินการพบว่าเครื่องมือวิเคราะห์แบบสอบถามแยกเงื่อนไขออกเป็นs_guid < NEWID() OR s_guid > NEWID():

ภาพหน้าจอของแผนแบบสอบถาม

... ซึ่งอธิบายได้อย่างสมบูรณ์ว่าเหตุใดจึงล้มเหลวในบางครั้ง (ถ้า ID ที่สร้างขึ้นครั้งแรกมีขนาดเล็กลงและอีกรายการหนึ่งใหญ่กว่า ID ที่กำหนด)

คือ SQL Server ได้รับอนุญาตในการประเมินA <> Bเป็นA < B OR A > Bแม้ว่าหนึ่งของการแสดงออกไม่เป็นกำหนด? ถ้าใช่เอกสารนั้นอยู่ที่ไหน หรือว่าเราพบข้อผิดพลาด?

น่าสนใจให้AND NOT (s_guid = NEWID())ผลแผนการดำเนินการเดียวกัน (และผลลัพธ์แบบสุ่มเดียวกัน)

เราพบปัญหานี้เมื่อนักพัฒนาซอฟต์แวร์ต้องการยกเว้นแถวที่เลือกและใช้:

s_guid <> ISNULL(@someParameter, NEWID())

เป็น "ทางลัด" สำหรับ:

(@someParameter IS NULL OR s_guid <> @someParameter)

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


คำตอบ:


22

คือ SQL Server ได้รับอนุญาตในการประเมินA <> Bเป็นA < B OR A > Bแม้ว่าหนึ่งของการแสดงออกไม่เป็นกำหนด?

นี่เป็นประเด็นที่ถกเถียงกันและคำตอบก็คือ "ใช่"

การอภิปรายที่ดีที่สุดที่ฉันทราบได้รับคำตอบจากรายงานข้อผิดพลาดการเชื่อมต่อของ Itzik Ben-Gan Bug ที่มี NEWID และ Table Expressionsซึ่งถูกปิดเนื่องจากไม่สามารถแก้ไขได้ การเชื่อมต่อนั้นถูกยกเลิกการใช้งานดังนั้นลิงก์ที่มีไปยังที่เก็บถาวรเว็บ น่าเศร้าที่เนื้อหาที่มีประโยชน์มากมายหายไป (หรือหายากกว่า) จากการตายของ Connect อย่างไรก็ตามคำพูดที่มีประโยชน์ที่สุดจาก Jim Hogg ของ Microsoft มีดังนี้:

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

ก่อนตะโกนว่า "ไม่!" (ความชอบส่วนบุคคลของฉันเองเช่นกัน :-) พิจารณา: ข่าวดีก็คือใน 99% ของกรณีคำตอบเหมือนกัน ดังนั้นการเพิ่มประสิทธิภาพการค้นหาจึงเป็นชัยชนะที่ชัดเจน ข่าวร้ายก็คือถ้าแบบสอบถามมีรหัสผลข้างเคียงแล้วแผนที่แตกต่างกันแน่นอนสามารถให้ผลลัพธ์ที่แตกต่าง และ NEWID () เป็น 'ฟังก์ชั่น' ที่มีผลข้างเคียง (ไม่กำหนดค่า) ดังกล่าวซึ่งทำให้เกิดความแตกต่าง [ที่จริงแล้วถ้าคุณทำการทดลองคุณสามารถกำหนดรูปแบบอื่น ๆ ได้เช่นการประเมินลัดวงจรของ AND clauses: ทำให้ประโยคที่สองโยนประโยคเลขคณิตหารด้วยศูนย์ - การเพิ่มประสิทธิภาพที่แตกต่างกันอาจทำให้ประโยคที่สองก่อนประโยคแรก] คำอธิบายของ Craig ที่อื่นในหัวข้อนี้ SqlServer ไม่รับประกันว่าเมื่อใดที่ตัวดำเนินการสเกลา

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

จากทั้งหมดที่กล่าวมาเรากำลังย้ายเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหาในทิศทางของพฤติกรรม "ตามที่คาดไว้" สำหรับ NEWID () - ปิดการซื้อขายสำหรับ "ผลลัพธ์ตามที่คาดหวัง"

ตัวอย่างหนึ่งของการเปลี่ยนแปลงของพฤติกรรมในเรื่องนี้เมื่อเวลาผ่านไปเป็นNULLIF ทำงานไม่ถูกต้องกับฟังก์ชั่นที่ไม่ได้กำหนดเช่น RAND () นอกจากนี้ยังมีกรณีอื่น ๆ ที่คล้ายกันเช่นใช้COALESCEกับแบบสอบถามย่อยที่สามารถสร้างผลลัพธ์ที่ไม่คาดคิดและยังได้รับการแก้ไขอย่างค่อยเป็นค่อยไป

จิมพูดต่อ:

การปิดลูป . . ฉันได้พูดคุยคำถามนี้กับทีม Dev และในที่สุดเราก็ตัดสินใจที่จะไม่เปลี่ยนพฤติกรรมปัจจุบันด้วยเหตุผลดังต่อไปนี้:

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

2) "พฤติกรรมแบบครั้งเดียวต่อแถว" นี้ไม่ใช่ปัญหาใหม่แม้ว่าจะไม่ได้กล่าวถึงอย่างกว้างขวาง เราเริ่มปรับเปลี่ยนพฤติกรรมของมันในการเปิดตัวยูคอน แต่มันค่อนข้างยากที่จะปักลงไปอย่างแม่นยำในทุกกรณีความหมายที่แท้จริง! ตัวอย่างเช่นจะใช้กับแถวระหว่างกาลที่คำนวณ 'ระหว่างทาง' กับผลลัพธ์สุดท้ายหรือไม่ - ในกรณีนี้ขึ้นอยู่กับแผนการที่เลือกไว้อย่างชัดเจน หรือใช้กับแถวที่จะปรากฏในผลลัพธ์ที่เสร็จสมบูรณ์เท่านั้น? - มีการเรียกซ้ำที่น่ารังเกียจเกิดขึ้นที่นี่เพราะฉันแน่ใจว่าคุณจะเห็นด้วย!

3) ดังที่กล่าวไว้ก่อนหน้านี้เราใช้ค่าเริ่มต้นเป็น "เพิ่มประสิทธิภาพ" ซึ่งดีสำหรับ 99% ของกรณี 1% ของกรณีที่อาจเปลี่ยนผลลัพธ์ค่อนข้างง่ายที่จะสังเกตเห็น - ฟังก์ชั่น 'ผลกระทบ' ด้านข้างเช่น NEWID - และ 'แก้ไข' ที่ง่าย (การแลกเปลี่ยนที่สมบูรณ์เป็นผลมาจาก) ค่าเริ่มต้นนี้เป็น "เพิ่มประสิทธิภาพประสิทธิภาพ" อีกครั้งเป็นที่ยอมรับมายาวนานและได้รับการยอมรับ (ใช่ไม่ใช่ท่าทางที่คอมไพเลอร์เลือกใช้สำหรับภาษาการเขียนโปรแกรมทั่วไป แต่เป็นอย่างนั้น)

ดังนั้นคำแนะนำของเราคือ:

a) หลีกเลี่ยงการพึ่งพาเวลาที่ไม่รับประกันและความหมายของจำนวนการประหารชีวิต b) หลีกเลี่ยงการใช้ NEWID () ลึกลงไปในตารางนิพจน์ c) ใช้ OPTION เพื่อบังคับพฤติกรรมเฉพาะ (การซื้อขายที่สมบูรณ์แบบ)

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


น่าสนใจให้AND NOT (s_guid = NEWID())ผลแผนการดำเนินการเดียวกัน

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


ในกรณีนี้หากเราต้องการบังคับแผนเฉพาะที่ดูเหมือนว่าจะหลีกเลี่ยงปัญหาเราสามารถใช้ WITH (FORCESCAN) เพื่อความแน่นอนเราควรใช้ตัวแปรเพื่อเก็บผลลัพธ์ของ NEWID () ก่อนดำเนินการสืบค้น
Razvan Socol

11

เอกสารนี้ (เรียงลำดับ) ที่นี่:

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

ฟังก์ชั่นที่ผู้ใช้กำหนด

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

สิ่งที่สับสนมากที่สุดคือไม่ใช่ทุกฟังก์ชั่นที่ไม่ได้กำหนดค่าจริงทำงานแบบนี้ ตัวอย่างเช่น RAND () และ GETDATE () จะทำงานเพียงครั้งเดียวต่อการสืบค้น


มีการโพสต์บล็อกหรือคล้ายกันที่อธิบายว่าทำไม / เมื่อเครื่องมือจะแปลง "ไม่เท่ากับ" เป็นช่วงหรือไม่?
นาย Magoo

3
ไม่ใช่ที่ฉันรู้ อาจจะเป็นเพราะงานประจำ=, <และ>สามารถประเมินผลได้อย่างมีประสิทธิภาพกับ BTree
David Browne - Microsoft

5

สำหรับสิ่งที่คุ้มค่าถ้าคุณดูเอกสารมาตรฐาน SQL 92 เก่านี้ข้อกำหนดรอบ ๆ ความไม่เท่าเทียมกันได้อธิบายไว้ในส่วน " 8.2 <comparison predicate>" ดังนี้:

1) ให้ X และ Y เป็นองค์ประกอบคอนสตรัคเตอร์ <row value ที่เกี่ยวข้องสองรายการ ให้ XV และ YV เป็นค่าที่แทนด้วย X และ Y ตามลำดับ

[ ... ]

ii) "X <> Y" เป็นจริงถ้าและ XV และ YV ไม่เท่ากัน

[ ... ]

7) ให้ Rx และ Ry เป็นตัวสร้าง <row value constructor> s ของ <compar predicate> และให้ RXi และ RYi เป็น i-th <row value constructor element> s ของ Rx และ Ry ตามลำดับ "Rx <comp op> Ry" เป็นจริงเท็จหรือไม่รู้จักดังต่อไปนี้:

[ ... ]

b) "x <> Ry" เป็นจริงถ้า RXi <> RYi สำหรับ i บางตัวเท่านั้น

[ ... ]

h) "x <> Ry" เป็นเท็จถ้าหาก "Rx = Ry" เป็นจริง

หมายเหตุ: ฉันรวม 7b และ 7h เพื่อความสมบูรณ์เนื่องจากพวกเขาพูดเกี่ยวกับ<>การเปรียบเทียบ - ฉันไม่คิดว่าการเปรียบเทียบตัวสร้างค่าแถวที่มีหลายค่าถูกนำมาใช้ใน T-SQL เว้นแต่ว่าฉันแค่เข้าใจผิดอย่างมากเกี่ยวกับสิ่งที่กล่าว - ซึ่งค่อนข้างเป็นไปได้

นี่เป็นกลุ่มของขยะที่ทำให้สับสน แต่ถ้าคุณต้องการให้การดำน้ำถังขยะ ...

ฉันคิดว่า 1.ii เป็นรายการที่ใช้ในสถานการณ์นี้เนื่องจากเรากำลังเปรียบเทียบค่าของ "องค์ประกอบตัวสร้างค่าแถว"

ii) "X <> Y" เป็นจริงถ้าและ XV และ YV ไม่เท่ากัน

โดยพื้นฐานแล้วคำพูดX <> Yนั้นเป็นจริงหากค่าที่แทนด้วย X และ Y ไม่เท่ากัน เนื่องจากX < Y OR X > Yเป็นการเขียนเชิงตรรกะที่เทียบเท่ากันของคำกริยานั้นจึงยอดเยี่ยมมากสำหรับเครื่องมือเพิ่มประสิทธิภาพในการใช้งาน

มาตรฐานไม่ได้ใส่ข้อ จำกัด ใด ๆ ในคำนิยามนี้ที่เกี่ยวข้องกับ deterministic-ness (หรืออะไรก็ตามที่คุณได้รับ) ขององค์ประกอบตัวสร้างค่าแถวที่ด้านข้างของตัว<>ดำเนินการเปรียบเทียบ มันเป็นความรับผิดชอบของรหัสผู้ใช้ที่จะจัดการกับความจริงที่ว่าการแสดงออกของค่าในด้านหนึ่งอาจไม่ได้กำหนดไว้


1
ฉันจะ refarin จากการลงคะแนน (ขึ้นหรือลง) แต่ฉันไม่มั่นใจ คำพูดที่คุณให้เอ่ยถึง"คุณค่า" ความเข้าใจของฉันคือการเปรียบเทียบอยู่ระหว่างสองค่าหนึ่งค่าในแต่ละด้าน ไม่ใช่ระหว่าง instantiations สอง (หรือมากกว่า) ของค่าในแต่ละด้าน นอกจากนี้มาตรฐาน (อย่างน้อย 92 คุณพูด) ไม่ได้พูดถึงฟังก์ชั่นที่ไม่ได้กำหนดไว้ทั้งหมด ด้วยเหตุผลที่คล้ายคลึงกับของคุณเราสามารถสันนิษฐานได้ว่าผลิตภัณฑ์ SQL ที่เป็นไปตามมาตรฐานนั้นไม่ได้มีฟังก์ชั่นที่ไม่ได้กำหนดไว้ แต่มีเพียงสิ่งที่กล่าวถึงในมาตรฐาน
ypercubeᵀᴹ

@ ปีขอบคุณสำหรับข้อเสนอแนะ! ฉันคิดว่าการตีความของคุณถูกต้องแน่นอน นี่เป็นครั้งแรกที่ฉันได้อ่านเอกสารนั้น มันพูดถึงค่าในบริบทของค่าที่แสดงโดย "ตัวสร้างค่าแถว" ซึ่งเป็นที่อื่นในเอกสารที่กล่าวว่าสามารถเป็นแบบสอบถามย่อยแบบสเกลาร์ (เหนือสิ่งอื่นใด) โดยเฉพาะอย่างยิ่งแบบสอบถามย่อยแบบสเกลาร์ดูเหมือนว่าจะไม่เป็นไปตามที่คาดการณ์ไว้ แต่ฉันไม่รู้จริงๆว่าฉันกำลังพูดถึงเรื่องอะไร =)
Josh Darnell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.