Chỉ mục được nhóm 'Tìm kiếm vị ngữ' và 'vị ngữ' trên cùng một cột


7

Tôi có một cột khóa chính của chỉ mục được nhóm và tôi đang thực hiện một truy vấn phạm vi trên nó. Vấn đề là quá trình quét chỉ sử dụng phần phạm vi đầu tiên cho vị từ tìm kiếm, để lại phía bên kia của phạm vi là một vị từ còn lại. nguyên nhân gây ra việc đọc tất cả các hàng lên đến giới hạn @upper

Tôi đang sử dụng hai tham số cho phạm vi:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from messages
where msg_id between @lower+1 and @upper;

Trong trường hợp đó, kế hoạch thực hiện thực tế cho thấy:

  • vị ngữ: message.msg_id> = thấp hơn
  • tìm kiếm vị ngữ: Messages.msg_id <@upper
  • hàng đọc: 1005

Định nghĩa bảng (Đơn giản hóa):

CREATE TABLE [dbo].[messages](
    [msg_id] [numeric](18, 0) IDENTITY(0,1) NOT NULL,
    [col2] [varchar](32) NOT NULL,
 CONSTRAINT [PK_Message] PRIMARY KEY CLUSTERED 
(
    [msg_id] ASC
) 

Thêm thông tin

Khi được sử dụng với các hằng số thay vì các biến, cả hai vị từ đều 'Tìm kiếm' đã thử Option (Optimize for (@lower=1000)), nhưng không thành công

Câu trả lời:


8

Bắt đầu với truy vấn ban đầu của bạn:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower+1 and @upper;

Cái 1mà bạn đã thêm có một kiểu dữ liệu integertheo mặc định. Khi thêm một integergiá trị vào một numeric(18,0)giá trị SQL Server sẽ áp dụng các quy tắc ưu tiên kiểu dữ liệu . intcó mức độ ưu tiên thấp hơn để nó được chuyển đổi thành a numeric(1,0). Truy vấn của bạn tương đương như sau:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower+CAST(1 AS NUMERIC(1, 0)) and @upper;

Một bộ quy tắc khác nhau xung quanh Độ chính xác, tỷ lệ và Độ dài được áp dụng để xác định loại dữ liệu của biểu thức liên quan @lower. Không an toàn khi chỉ sử dụng NUMERIC(18,0)vì có thể bị tràn (xem xét 999.999.999.999.999.999 và 1 làm ví dụ). Quy tắc áp dụng ở đây là:

╔═══════════╦═════════════════════════════════════╦════════════════╗
 Operation           Result precision            Result scale * 
╠═══════════╬═════════════════════════════════════╬════════════════╣
 e1 + e2    max(s1, s2) + max(p1-s1, p2-s2) + 1  max(s1, s2)    
╚═══════════╩═════════════════════════════════════╩════════════════╝

Đối với biểu thức của bạn, độ chính xác kết quả là:

max(0, 0) + max(18 - 0, 1 - 0) + 1 = 0 + 18 + 1 = 19

và thang đo kết quả là 0. Bạn có thể xác minh điều này bằng cách chạy đoạn mã sau trong SQL Server:

declare
@lower numeric(18,0) = 1000,
@upper numeric(18,0) = 1005;

SELECT 
  SQL_VARIANT_PROPERTY(@lower+1, 'BaseType') lower_exp_BaseType
, SQL_VARIANT_PROPERTY(@lower+1, 'Precision') lower_exp_Precision
, SQL_VARIANT_PROPERTY(@lower+1, 'Scale') lower_exp_Scale;

Điều này có nghĩa là truy vấn ban đầu của bạn tương đương như sau:

declare
    @lower numeric(19,0) = 1000 + 1,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between @lower and @upper;

SQL Server chỉ có thể sử dụng @lowerđể thực hiện tìm kiếm chỉ mục cụm nếu giá trị có thể được chuyển đổi hoàn toàn thành NUMERIC(18, 0). Nó không an toàn để chuyển đổi một NUMERIC(19,0)giá trị sang NUMERIC(18,0). Kết quả là giá trị được áp dụng như một vị từ thay vì như một vị từ tìm kiếm. Một cách giải quyết là làm như sau:

declare
    @lower numeric(18,0) = 1000,
    @upper numeric(18,0) = 1005;

select * from [messages]
where msg_id between TRY_CAST(@lower+1 AS NUMERIC(18,0)) and @upper;

Truy vấn đó có thể xử lý cả hai bộ lọc dưới dạng tìm kiếm vị từ:

tìm kiếm vị ngữ

Lời khuyên của tôi là thay đổi kiểu dữ liệu trong bảng thành BIGINTnếu có thể. BIGINTyêu cầu ít hơn một byte so với NUMERIC(18,0)và lợi ích từ tối ưu hóa hiệu suất không có sẵn để NUMERIC(18,0)bao gồm hỗ trợ tốt hơn cho các bộ lọc bitmap.


4

Có một biểu thức trên một trong các bộ lọc của bạn ( @lower+1) đang làm cho động cơ thực hiện một biến vị ngữ thông thường chứ không phải là một vị từ tìm kiếm (làm cho nó không có khả năng SARG).

Hãy thử thay đổi giá trị của bộ lọc trước SELECTcâu lệnh và bạn sẽ thấy rằng cả hai đầu sẽ được sử dụng chính xác làm ranh giới tìm kiếm.

declare
    @lower numeric(18,0) = 1000 + 1,
    @upper numeric(18,0) = 1005;

select * from messages
where msg_id between @lower and @upper;

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

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.