Tại sao chỉ mục chọn lọc thứ cấp không được sử dụng khi mệnh đề where lọc trên `value ()`?


13

Thiết lập:

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 mẫu cho mỗi hàng:

<Number>314</Number>

Công việc cho truy vấn là đếm số lượng hàng trong T với giá trị được chỉ định là <Number>.

Có hai cách rõ ràng để làm điều này:

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;

Nó chỉ ra rằng value()exists() yêu cầu hai định nghĩa đường dẫn khác nhau để chỉ mục XML chọn lọc hoạt động.

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

Các sqlphiên bản là chovalue()xqueryphiên bản là cho exist().

Bạn có thể nghĩ rằng một chỉ mục như thế sẽ cung cấp cho bạn một kế hoạch với một tìm kiếm tốt nhưng các chỉ mục XML có chọn lọc được triển khai như một bảng hệ thống với khóa chính là T là khóa chính của khóa cụm của bảng hệ thống. Các đường dẫn được chỉ định là các cột thưa thớt trong bảng đó. Nếu bạn muốn một chỉ mục của các giá trị thực tế của các đường dẫn được xác định, bạn cần tạo một chỉ mục chọn lọc thứ cấp, một chỉ mục cho mỗi biểu thức đường dẫn.

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);

Kế hoạch truy vấn để exist()tìm kiếm trong chỉ mục XML thứ cấp theo sau là tra cứu khóa trong bảng hệ thống cho chỉ mục XML chọn lọc (không biết tại sao lại cần thiết) và cuối cùng nó sẽ tra cứu Tđể đảm bảo thực sự có hàng trong đó. Phần cuối cùng là cần thiết vì không có ràng buộc khóa ngoài giữa bảng hệ thống và T.

nhập mô tả hình ảnh ở đây

Kế hoạch cho value()truy vấn không phải là tốt đẹp. Nó thực hiện quét chỉ mục cụmT với một vòng lặp lồng nhau tham gia tìm kiếm trên bảng bên trong để lấy giá trị từ cột thưa thớt và cuối cùng là lọc các giá trị.

nhập mô tả hình ảnh ở đây

Nếu một chỉ số chọn lọc nên được sử dụng hay không được quyết định trước khi tối ưu hóa nhưng nếu chỉ số chọn lọc thứ cấp có nên được sử dụng hay không thì đó là quyết định dựa trên chi phí của trình tối ưu hóa.

Tại sao chỉ mục chọn lọc thứ cấp không được sử dụng khi mệnh đề where lọc trênvalue() ?

Cập nhật:

Các truy vấn là khác nhau về ngữ nghĩa. Nếu bạn thêm một hàng với giá trị

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

các exist()phiên bản sẽ đếm 2 hàng và các values()truy vấn sẽ đếm 1 hàng. Nhưng với các định nghĩa chỉ mục như chúng được chỉ định ở đây bằng cách sử dụng singletonSQL Server chỉ thị sẽ ngăn bạn thêm một hàng có nhiều hàng<Number> phần tử.

Tuy nhiên, điều đó không cho phép chúng tôi sử dụng values()hàm mà không chỉ định [1]để đảm bảo trình biên dịch rằng chúng tôi sẽ chỉ nhận được một giá trị duy nhất. Đó [1]là lý do chúng tôi có Top N Sắp xếp trongvalue() kế hoạch.

Có vẻ như tôi đang kết thúc câu trả lời ở đây ...

Câu trả lời:


11

Khai báo singletontrong biểu thức đường dẫn của chỉ mục thực thi rằng bạn không thể thêm nhiều <Number>phần tử nhưng trình biên dịch XQuery không xem xét điều đó khi diễn giải biểu thức trong value()hàm. Bạn phải xác định[1] để làm cho SQL Server hài lòng. Việc sử dụng XML được gõ với một lược đồ cũng không giúp được gì. Và do đó, SQL Server xây dựng một truy vấn sử dụng một cái gì đó có thể được gọi là mẫu "áp dụng".

Dễ nhất để chứng minh là sử dụng các bảng thông thường thay vì XML mô phỏng truy vấn mà chúng ta thực sự đang thực hiện Tvà bảng nội bộ.

Đây là thiết lập cho bảng nội bộ như một bảng thực.

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;

Với cả hai bảng tại chỗ, bạn có thể thực hiện tương đương với exist()truy vấn.

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
             );

Tương đương của value()truy vấn sẽ như thế này.

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;

Các top(1)order by S.path_1_idlà thủ phạm và nó là [1]trong biểu thức XPath mà là để đổ lỗi.

Tôi không nghĩ rằng nó có thể cho Microsoft để sửa lỗi này với cấu trúc hiện tại của bảng nội ngay cả khi bạn được phép bỏ qua các [1]từ values()chức năng. Họ có thể sẽ phải tạo nhiều bảng nội bộ cho mỗi biểu thức đường dẫn với các ràng buộc duy nhất để đảm bảo cho trình tối ưu hóa rằng chỉ có thể có một <number>phần tử cho mỗi hàng. Không chắc chắn rằng điều đó thực sự đủ để trình tối ưu hóa "thoát ra khỏi mô hình áp dụng".

Đối với bạn, những người nghĩ rằng điều này thú vị và thú vị và vì bạn vẫn đang đọc nó, có lẽ bạn đang đọc.

Một số truy vấn để xem cấu trúc của bảng nội bộ.

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')
                     );
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.