SQL Server ล้มเหลวในการใช้ดัชนีใน bijection ง่าย


11

นี่เป็นปริศนาเพิ่มประสิทธิภาพการสืบค้นอีกอันหนึ่ง

บางทีฉันแค่ประมาณเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหาหรือบางทีฉันอาจพลาดบางสิ่ง - ดังนั้นฉันจึงวางมันไว้ที่นั่น

ฉันมีโต๊ะธรรมดา

CREATE TABLE [dbo].[MyEntities](
  [Id] [uniqueidentifier] NOT NULL,
  [Number] [int] NOT NULL,
  CONSTRAINT [PK_dbo.MyEntities] PRIMARY KEY CLUSTERED ([Id])
)

CREATE NONCLUSTERED INDEX [IX_Number] ON [dbo].[MyEntities] ([Number])

ด้วยดัชนีและแถวบางแถวในนั้นมีNumberการกระจายอย่างเท่าเทียมกันในค่า 0, 1 และ 2

ตอนนี้แบบสอบถามนี้:

SELECT * FROM
    (SELECT
        [Extent1].[Number] AS [Number],
        CASE
        WHEN (0 = [Extent1].[Number]) THEN 'one'
        WHEN (1 = [Extent1].[Number]) THEN 'two'
        WHEN (2 = [Extent1].[Number]) THEN 'three'
        ELSE '?'
        END AS [Name]
        FROM [dbo].[MyEntities] AS [Extent1]
        ) P
WHERE P.Number = 0;

ดัชนีพยายามIX_Numberตามที่คาดหวังหรือไม่

หากข้อไหนเป็น

WHERE P.Name = 'one';

อย่างไรก็ตามจะเป็นการสแกน

เห็นได้ชัดว่า case-clause นั้นเป็น bijection ดังนั้นในทางทฤษฎีแล้วการเพิ่มประสิทธิภาพควรเป็นไปได้ที่จะหักแผนคิวรีแรกออกจากเคียวรีที่สอง

นอกจากนี้ยังไม่ใช่เชิงวิชาการอย่างแท้จริง: ข้อความค้นหาได้รับแรงบันดาลใจจากการแปลค่า enum เป็นชื่อที่เป็นมิตรของพวกเขา

ฉันต้องการที่จะได้ยินจากคนที่รู้ว่าสิ่งที่สามารถคาดหวังจากการเพิ่มประสิทธิภาพแบบสอบถาม (และโดยเฉพาะอย่างยิ่งหนึ่งในเซิร์ฟเวอร์ SQL): ฉันแค่คาดหวังมากเกินไป?

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

ฉันใช้ Sql Server 2016 Developer Edition

คำตอบ:


18

ฉันคาดหวังมากเกินไปหรือไม่

ใช่. อย่างน้อยในเวอร์ชันปัจจุบันของผลิตภัณฑ์

SQL Server จะไม่แยกCASEคำสั่งนั้นและทำวิศวกรรมย้อนกลับเพื่อค้นหาว่าถ้าผลลัพธ์ของคอลัมน์ที่คำนวณ'one'นั้น[Extent1].[Number]ต้องเป็น0เช่นนั้น

คุณต้องตรวจสอบให้แน่ใจว่าคุณเขียนภาคแสดงของคุณเพื่อให้สามารถระบุเป้าหมายได้ ซึ่งเกือบจะเกี่ยวข้องกับมันอยู่ในรูปแบบ basetable_column_name comparison_operator expression.

แม้แต่การเบี่ยงเบนเล็กน้อยก็ทำลายความซบเซา

WHERE P.Number + 0 = 0;

จะไม่ใช้ดัชนีแสวงหาเช่นกันแม้ว่ามันจะตรงไปตรงมากว่าเพื่อทำให้การCASEแสดงออกง่ายขึ้น

หากคุณต้องการค้นหาชื่อสตริงและค้นหาหมายเลขคุณจะต้องมีตารางการทำแผนที่ที่มีชื่อและหมายเลขและเข้าร่วมในแบบสอบถามดังนั้นแผนอาจมีการค้นหาบนตารางการทำแผนที่ตามด้วยการค้นหาที่สัมพันธ์กัน ใน[dbo].[MyEntities]ที่มีจำนวนที่ส่งกลับมาจากครั้งแรกแสวงหา


6

อย่าฉาย enum ของคุณเป็นคำสั่งกรณี ฉายมันเป็นตารางที่ได้รับเช่น:

SELECT * FROM
   (SELECT
      [Extent1].[Number] AS [Number],
      enum.Name
   FROM
      [dbo].[MyEntities] AS [Extent1]
      LEFT JOIN (VALUES
         (0, 'one'),
         (1, 'two'),
         (2, 'three')
      ) enum (Number, Name)
         ON Extent1.Number = enum.Number
   ) P
WHERE
   P.Name = 'one';

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

ฉันคาดเดาว่าเนื่องจาก[Extent1]ในนั้นคุณกำลังใช้ ORM เช่น Entity Framework หรือ Linq-To-SQL ฉันไม่สามารถแนะนำวิธีการฉายภาพให้สำเร็จได้ แต่คุณสามารถใช้เทคนิคอื่นได้

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

ตอนนี้ฉันกำลังใช้Identifierคลาสพื้นฐานที่มีคลาสย่อยที่แตกต่างกันมากมาย แต่ไม่มีเหตุผลที่มันไม่สามารถทำได้ด้วยวานิลลาอีนูมธรรมดา นี่คือตัวอย่างการใช้งาน:

new EnumOrIdentifierProjector<CodeClassOrEnum, PrivateDbDtoObject>(
   _sqlConnector.Connection,
   "dbo.TableName",
   "PrimaryKeyId",
   "NameColumnName",
   dtoObject => dtoObject.PrimaryKeyId,
   dtoObject => dtoObject.NameField,
   EnumerableOfIdentifierOrTypeOfEnum
)
   .Populate();

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

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

ฉันหวังว่าความคิดเหล่านี้เพียงพอสำหรับคุณในการปรับปรุง


ใช่ฉันใช้ EntityFramework และมีที่ที่ทางออกควรอยู่ในโลกที่ดีที่สุด ก่อนหน้านั้นเกิดขึ้นคำแนะนำของคุณคือหนึ่งในวิธีการแก้ปัญหาที่ดีที่สุดที่ฉันเชื่อ
จอห์น

5

ฉันตีความคำถามตามที่คุณสนใจในเครื่องมือเพิ่มประสิทธิภาพโดยทั่วไป แต่ด้วยความสนใจเป็นพิเศษสำหรับ SQL Server ฉันทดสอบสถานการณ์ของคุณด้วย db2 LUW V11.1:

]$ db2 "create table myentities ( id int not null, number int not null )"
]$ db2 "create index ix_number on myentities (number)"
]$ db2 "insert into myentities (id, number) with t(n) as ( values 0 union all select n+1 from t where n<10000) select n, mod(n,3) from t"

เครื่องมือเพิ่มประสิทธิภาพใน DB2 จะเขียนแบบสอบถามที่สองไปยังแบบสอบถามแรก:

Original Statement:
------------------
SELECT 
  * 
FROM 
  (SELECT 
     number,

   CASE 
   WHEN (0 = Number) 
   THEN 'one' 
   WHEN (1 = Number) 
   THEN 'two' 
   WHEN (2 = Number) 
   THEN 'three' 
   ELSE '?' END AS Name 
   FROM 
     MyEntities
  ) P 
WHERE 
  P.name = 'one'


Optimized Statement:
-------------------
SELECT 
  Q1.NUMBER AS "NUMBER",

CASE 
WHEN (0 = Q1.NUMBER) 
THEN 'one' 
WHEN (1 = Q1.NUMBER) 
THEN 'two' 
WHEN (2 = Q1.NUMBER) 
THEN 'three' 
ELSE '?' END AS "NAME" 
FROM 
  LELLE.MYENTITIES AS Q1 
WHERE 
  (0 = Q1.NUMBER)

แผนดูเหมือนว่า:

Access Plan:
-----------
        Total Cost:             33.5483
        Query Degree:           1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
      3334 
     IXSCAN
     (   2)
     33.1861 
     4.66713 
       |
      10001 
 INDEX: LELLE   
    IX_NUMBER
       Q1

ฉันไม่รู้อะไรเกี่ยวกับเครื่องมือเพิ่มประสิทธิภาพอื่น ๆ แต่ฉันรู้สึกว่าเครื่องมือเพิ่มประสิทธิภาพ DB2 นั้นถือว่าค่อนข้างดีแม้ในหมู่คู่แข่ง


นั่นเป็นเรื่องที่น่าตื่นเต้น คุณช่วยส่องแสงที่คำสั่งที่ปรับให้เหมาะสมมาจากไหนได้บ้าง db2 ส่งคืนให้คุณหรือไม่? - นอกจากนี้ฉันมีปัญหาในการอ่านแผน ฉันใช้มัน "IXSCAN" ไม่ได้หมายถึงการสแกนดัชนีในกรณีนี้?
จอห์น

1
คุณสามารถบอก DB2 ให้อธิบายคำสั่งให้คุณ ข้อมูลที่เก็บรวบรวมจะถูกเก็บไว้ในชุดของตารางและคุณสามารถใช้อธิบายภาพหรือในกรณีนี้ยูทิลิตี้ db2exfmt (หรือสร้าง Ut ของคุณเอง) นอกจากนี้คุณสามารถตรวจสอบคำสั่งและเปรียบเทียบ cardinality โดยประมาณในแผนกับแผนจริง ในแผนนี้เราจะเห็นว่ามันเป็นดัชนีสแกนเนอร์ (IXSCAN) และผลลัพธ์โดยประมาณจากโอเปอเรเตอร์นี้คือ 3334 แถว สิ่งนี้เลวในเซิร์ฟเวอร์ SQL หรือไม่ มันรู้จักปุ่มเริ่มต้นและปุ่มหยุดดังนั้นมันจะสแกนเฉพาะแถวที่เกี่ยวข้องใน DB2
Lennart

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

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

0

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

SELECT
    [Extent1].[Number] AS [Number],
    'one' AS [Name]
FROM [dbo].[MyEntities] AS [Extent1]
WHERE [Extent1].[Number] = 0;

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


1
ฉันคิดว่าคุณไม่มีจุด - นี่คือ SQL ที่สร้างขึ้นจากฐานข้อมูลส่วนหลังที่ทำงานร่วมกับ enums ผ่านการเป็นตัวแทนสตริง รหัสที่ฉาย SQL กำลังใช้ความรุนแรงกับแบบสอบถาม ฉันแน่ใจว่าผู้ถามถ้าเขาเขียน SQL เองจะสามารถเขียนแบบสอบถามที่ดีกว่าได้ ดังนั้นจึงไม่น่าเบื่อที่จะต้องมีCASEคำสั่งใด ๆ เพราะ ORM ทำสิ่งนั้น โง่คืออะไรที่คุณไม่รู้จักแง่มุมที่เรียบง่ายของปัญหา ... (วิธีการที่เรียกว่างี่เง่าทางอ้อม)
ErikE

@ErikE ยังคงประเภทโง่เพราะคุณก็สามารถใช้ตัวเลขมูลค่าของ enum สมมติว่า C # อยู่แล้ว (สมมติฐานที่ค่อนข้างปลอดภัยเนื่องจากเรากำลังพูดถึง SQL Server)
jpmc26

แต่คุณไม่ทราบว่ากรณีการใช้งานคืออะไร บางทีมันอาจเป็นการเปลี่ยนแปลงครั้งใหญ่ในการเปลี่ยนไปใช้ค่าตัวเลข บางที enums ถูกดัดแปลงเพิ่มเติมในฐานรหัสยักษ์ที่มีอยู่ การวิพากษ์วิจารณ์โดยปราศจากความรู้นั้นไร้สาระ
ErikE

@ErikE ถ้ามันไร้สาระแล้วทำไมคุณถึงทำอย่างนั้น? =) ฉันตอบเพียงเพื่อชี้ให้เห็นว่าหากกรณีการใช้งานนั้นเรียบง่ายเหมือนตัวอย่างในคำถาม (ซึ่งระบุไว้อย่างชัดเจนในคำนำของคำตอบของฉัน) CASEคำสั่งนั้นจะถูกกำจัดโดยสิ้นเชิงโดยไม่มีข้อเสียเปรียบ ของหลักสูตรอาจจะมีปัจจัยที่ไม่รู้จัก แต่พวกเขาไม่ได้ระบุ
jpmc26

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