ทำไมดัชนีการเลือกรองไม่ได้ใช้เมื่อตัวกรองส่วนคำสั่ง where บน value () `?


13

ติดตั้ง:

create table dbo.T
(
  ID int identity primary key,
  XMLDoc xml not null
);

insert into dbo.T(XMLDoc)
select (
       select N.Number
       for xml path(''), type
       )
from (
     select top(10000) row_number() over(order by (select null)) as Number
     from sys.columns as c1, sys.columns as c2
     ) as N;

ตัวอย่าง XML สำหรับแต่ละแถว:

<Number>314</Number>

งานสำหรับการค้นหาคือการนับจำนวนแถวในที่มีค่าที่กำหนดT<Number>

มีสองวิธีที่ชัดเจนในการทำสิ่งนี้:

select count(*)
from dbo.T as T
where T.XMLDoc.value('/Number[1]', 'int') = 314;

select count(*)
from dbo.T as T
where T.XMLDoc.exist('/Number[. eq 314]') = 1;

ปรากฎว่าvalue()และexists()ต้องการนิยามพา ธ สองแบบที่แตกต่างกันเพื่อให้ดัชนี XML แบบเลือกทำงานได้

create selective xml index SIX_T on dbo.T(XMLDoc) for
(
  pathSQL = '/Number' as sql int singleton,
  pathXQUERY = '/Number' as xquery 'xs:double' singleton
);

sqlรุ่นสำหรับvalue()และรุ่นสำหรับxqueryexist()

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

create xml index SIX_T_pathSQL on dbo.T(XMLDoc)
  using xml index SIX_T for (pathSQL);

create xml index SIX_T_pathXQUERY on dbo.T(XMLDoc)
  using xml index SIX_T for (pathXQUERY);

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ทำไมดัชนีการคัดเลือกรองไม่ได้ใช้เมื่อที่ฟิลเตอร์ข้อบนvalue()?

ปรับปรุง:

แบบสอบถามแตกต่างกันในเชิงความหมาย หากคุณเพิ่มแถวด้วยค่า

<Number>313</Number>
<Number>314</Number>` 

exist()รุ่นจะนับ 2 แถวและvalues()แบบสอบถามจะนับ 1 แถว แต่ด้วยนิยามดัชนีตามที่ระบุไว้ที่นี่โดยใช้singletonคำสั่ง SQL Server จะป้องกันคุณจากการเพิ่มแถวที่มี<Number>องค์ประกอบหลายรายการ

อย่างไรก็ตามนั่นไม่อนุญาตให้เราใช้values()ฟังก์ชั่นโดยไม่ระบุ[1]เพื่อรับประกันคอมไพเลอร์ว่าเราจะได้รับค่าเดียวเท่านั้น นั่น[1]คือเหตุผลที่เรามี Top N Sort ในvalue()แผน

ดูเหมือนว่าฉันจะปิดคำตอบที่นี่ ...

คำตอบ:


11

การประกาศsingletonในนิพจน์พา ธ ของดัชนีบังคับให้คุณไม่สามารถเพิ่ม<Number>องค์ประกอบหลายรายการได้แต่คอมไพเลอร์ XQuery ไม่ได้พิจารณาสิ่งนั้นเมื่อพิจารณาการตีความนิพจน์ในvalue()ฟังก์ชัน คุณต้องระบุ[1]ให้ SQL Server มีความสุข การใช้ XML ที่พิมพ์ด้วยสคีมานั้นไม่ได้ช่วยอะไรเช่นนั้น และเนื่องจาก SQL Server นั้นสร้างคิวรีที่ใช้บางสิ่งที่อาจเรียกได้ว่าเป็นรูปแบบ "ใช้"

ที่ง่ายที่สุดในการสาธิตคือการใช้ตารางปกติแทน XML จำลองแบบสอบถามที่เรากำลังดำเนินการกับTตารางภายใน

นี่คือการตั้งค่าสำหรับตารางภายในเป็นตารางจริง

create table dbo.xml_sxi_table
(
  pk1 int not null,
  row_id int,
  path_1_id varbinary(900),
  pathSQL_1_sql_value int,
  pathXQUERY_2_value float
);

go

create clustered index SIX_T on xml_sxi_table(pk1, row_id);
create nonclustered index SIX_pathSQL on xml_sxi_table(pathSQL_1_sql_value) where path_1_id is not null;
create nonclustered index SIX_T_pathXQUERY on xml_sxi_table(pathXQUERY_2_value) where path_1_id is not null;

go

insert into dbo.xml_sxi_table(pk1, row_id, path_1_id, pathSQL_1_sql_value, pathXQUERY_2_value)
select T.ID, 1, T.ID, T.ID, T.ID
from dbo.T;

ด้วยตารางทั้งสองที่คุณสามารถดำเนินการเทียบเท่าของexist()แบบสอบถาม

select count(*)
from dbo.T
where exists (
             select *
             from dbo.xml_sxi_table as S
             where S.pk1 = T.ID and
                   S.pathXQUERY_2_value = 314 and
                   S.path_1_id is not null
             );

value()ข้อความค้นหาที่เทียบเท่าจะมีลักษณะเช่นนี้

select count(*)
from dbo.T
where (
      select top(1) S.pathSQL_1_sql_value
      from dbo.xml_sxi_table as S
      where S.pk1 = T.ID and
            S.path_1_id is not null
      order by S.path_1_id
      ) = 314;

top(1)และorder by S.path_1_idเป็นผู้ร้ายและมันก็เป็น[1]ในนิพจน์ XPath ที่จะตำหนิ

ฉันไม่คิดว่าเป็นไปได้ที่ Microsoft จะแก้ไขปัญหานี้ด้วยโครงสร้างปัจจุบันของตารางภายในแม้ว่าคุณจะได้รับอนุญาตให้ออก[1]จากvalues()ฟังก์ชัน พวกเขาอาจจะต้องสร้างตารางภายในหลายตารางสำหรับแต่ละนิพจน์พา ธ ที่มีข้อ จำกัด ที่ไม่ซ้ำกันเพื่อรับประกันสำหรับเครื่องมือเพิ่มประสิทธิภาพที่สามารถมีได้เพียงหนึ่ง<number>องค์ประกอบสำหรับแต่ละแถว ไม่แน่ใจว่าจริง ๆ แล้วจะเพียงพอสำหรับเครื่องมือเพิ่มประสิทธิภาพ "แยกออกจากรูปแบบนำไปใช้"

สำหรับคุณที่คิดว่ามันสนุกและน่าสนใจและเนื่องจากคุณยังคงอ่านสิ่งนี้อยู่คุณอาจจะเป็น

แบบสอบถามบางอย่างเพื่อดูโครงสร้างของตารางภายใน

select T.name, 
       T.internal_type_desc, 
       object_name(T.parent_id) as parent_table_name
from sys.internal_tables as T
where T.parent_id = object_id('T');

select C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       C.is_sparse,
       C.is_nullable
from sys.columns as C
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where C.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     )
order by C.column_id;

select I.name as index_name,
       I.type_desc,
       I.is_unique,
       I.filter_definition,
       IC.key_ordinal,
       C.name as column_name, 
       C.column_id,
       T.name as type_name,
       C.max_length,
       I.is_unique,
       I.is_unique_constraint
from sys.indexes as I
  inner join sys.index_columns as IC
    on I.object_id = IC.object_id and
       I.index_id = IC.index_id
  inner join sys.columns as C
    on IC.column_id = C.column_id and
       IC.object_id = C.object_id
  inner join sys.types as T
    on C.user_type_id = T.user_type_id
where I.object_id in (
                     select T.object_id 
                     from sys.internal_tables as T 
                     where T.parent_id = object_id('T')
                     );
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.