Sql Server không sử dụng chỉ mục trên đơn giản


11

Đây là một câu hỏi hóc búa tối ưu hóa truy vấn khác.

Có lẽ tôi chỉ ước tính quá mức tối ưu hóa truy vấn hoặc có thể tôi đang thiếu một cái gì đó - vì vậy tôi sẽ đưa nó ra ngoài đó.

Tôi có một cái bàn đơn giản

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

với một chỉ mục và hàng ngàn hàng trong đó, Numberđược phân bổ đều trong các giá trị 0, 1 và 2.

Bây giờ truy vấn này:

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;

không một chỉ số tìm kiếm IX_Numbernhư người ta mong đợi.

Nếu mệnh đề where là

WHERE P.Name = 'one';

tuy nhiên, nó trở thành quét.

Mệnh đề trường hợp rõ ràng là một mệnh đề, vì vậy về mặt lý thuyết, tối ưu hóa có thể được khấu trừ kế hoạch truy vấn đầu tiên từ truy vấn thứ hai.

Nó cũng không hoàn toàn mang tính học thuật: Truy vấn được lấy cảm hứng bằng cách dịch các giá trị enum sang tên thân thiện tương ứng của chúng.

Tôi muốn nghe từ một người biết những gì có thể được mong đợi từ các trình tối ưu hóa truy vấn (và cụ thể là câu hỏi trong Sql Server): Tôi chỉ đơn giản là mong đợi quá nhiều?

Tôi đang hỏi khi tôi gặp trường hợp trước khi một số biến thể nhỏ của truy vấn sẽ làm cho việc tối ưu hóa đột nhiên được đưa ra ánh sáng.

Tôi đang sử dụng Sql Server 2016 Developer Edition.

Câu trả lời:


18

Tôi chỉ đơn giản là mong đợi quá nhiều?

Đúng. Ít nhất là trong các phiên bản hiện tại của sản phẩm.

SQL Server sẽ không chọn CASEcâu lệnh và thiết kế ngược lại để phát hiện ra rằng nếu kết quả của cột được tính toán 'one'thì [Extent1].[Number]phải 0.

Bạn cần chắc chắn rằng bạn viết các vị từ của bạn để có thể đọc được. Mà hầu như luôn luôn liên quan đến nó ở dạng. basetable_column_name comparison_operator expression.

Ngay cả những sai lệch nhỏ cũng phá vỡ tầm nhìn.

WHERE P.Number + 0 = 0;

sẽ không sử dụng một chỉ mục tìm kiếm mặc dù nó thậm chí còn đơn giản hơn để đơn giản hóa hơn CASEbiểu thức.

Nếu bạn muốn tìm kiếm tên chuỗi và tìm kiếm số, bạn sẽ cần một bảng ánh xạ với tên và số và tham gia vào nó trong truy vấn, kế hoạch có thể có một tìm kiếm trên bảng ánh xạ theo sau là tìm kiếm tương quan trên [dbo].[MyEntities]với số được trả về từ lần tìm kiếm đầu tiên.


6

Đừng chiếu enum của bạn như một tuyên bố trường hợp. Chiếu nó như một bảng dẫn xuất như vậy:

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

Tôi nghi ngờ bạn sẽ nhận được kết quả tốt hơn. (Tôi đã không chuyển đổi Tên thành ?khi bị thiếu vì điều này có thể ảnh hưởng đến khả năng tăng hiệu suất. Tuy nhiên, bạn có thể di chuyển WHEREmệnh đề bên trong truy vấn bên ngoài để đặt vị từ trên enumbảng hoặc bạn có thể trả về hai cột từ truy vấn bên trong, một cho vị ngữ và một cho hiển thị, trong đó một vị từ là NULLkhi không có giá trị enum phù hợp.)

Tuy nhiên, tôi đoán rằng do [Extent1]đó, bạn đang sử dụng một ORM như Entity Framework hoặc Linq-To-SQL. Tôi không thể hướng dẫn bạn cách thực hiện một phép chiếu như vậy, nhưng, bạn có thể sử dụng một kỹ thuật khác.

Trong một dự án của tôi, tôi đã phản ánh các giá trị enum mã trong các bảng thực trong cơ sở dữ liệu, thông qua một lớp xây dựng tùy chỉnh đã hợp nhất các giá trị enum vào cơ sở dữ liệu. . .

Bây giờ, tôi đang sử dụng vô số một Identifierlớp cơ sở có nhiều lớp con cụ thể khác nhau, nhưng không có lý do gì nó không thể được thực hiện với một enum vanilla đơn giản. Đây là một ví dụ sử dụng:

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

Bạn có thể thấy rằng tôi đã chuyển tất cả các thông tin cần thiết để vừa viết vừa đọc các giá trị cơ sở dữ liệu. (Tôi đã gặp tình huống yêu cầu hiện tại có thể không chứa tất cả các giá trị còn tồn tại, do đó cần phải trả lại bất kỳ bổ sung nào từ cơ sở dữ liệu cũng như bộ hiện đang được tải. Tôi cũng để cơ sở dữ liệu gán ID, mặc dù đối với một enum bạn có thể sẽ không muốn điều đó.)

Ý tưởng là một khi bạn có một bảng chỉ đọc / ghi một lần khi khởi động sẽ có tất cả các giá trị enum, bạn chỉ cần tham gia vào nó như bất kỳ bảng nào khác và hiệu suất sẽ tốt.

Tôi hy vọng những ý tưởng này là đủ để bạn thực hiện một cải tiến.


Có, tôi sử dụng EntityFramework và trong đó có giải pháp thực sự nên ở trong một thế giới tối ưu. Trước khi điều đó xảy ra, đề nghị của bạn là một trong những cách giải quyết tốt nhất mà tôi tin.
Giăng

5

Tôi giải thích câu hỏi là bạn quan tâm đến các trình tối ưu hóa nói chung, nhưng với sự quan tâm đặc biệt dành cho SQL Server. Tôi đã thử nghiệm kịch bản của bạn với 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"

Trình tối ưu hóa trong DB2 viết lại truy vấn thứ hai thành truy vấn thứ nhất:

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)

Kế hoạch có vẻ như:

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

Tôi không biết nhiều về các trình tối ưu hóa khác, nhưng tôi có cảm giác rằng trình tối ưu hóa DB2 được coi là khá tốt ngay cả trong số các đối thủ cạnh tranh.


Điều đó thật thú vị. Bạn có thể làm sáng tỏ về "tuyên bố tối ưu hóa" đến từ đâu không? Liệu db2 có trả lại cho bạn không? - Ngoài ra, tôi gặp khó khăn khi đọc kế hoạch. Tôi lấy nó "IXSCAN" không có nghĩa là quét chỉ mục trong trường hợp này?
Giăng

1
Bạn có thể yêu cầu DB2 giải thích một tuyên bố cho bạn. Thông tin được thu thập được lưu trữ trong một tập hợp các bảng và bạn có thể sử dụng giải thích trực quan hoặc trong trường hợp này là tiện ích db2exfmt (hoặc tạo tiện ích của riêng bạn). Ngoài ra, bạn có thể theo dõi một tuyên bố và so sánh số lượng ước tính trong kế hoạch với kế hoạch thực tế. Trong kế hoạch này, chúng ta có thể thấy rằng nó thực sự là một indexscan (IXSCAN) và sản lượng ước tính từ toán tử này là 3334 hàng. Đây có phải là xấu trong máy chủ SQL? Nó biết startkey và stopkey nên nó chỉ quét các hàng có liên quan trong DB2.
Lennart

Vì vậy, những gì nó gọi là quét liên quan đến tìm kiếm, và thành thật mà nói, các giải thích kế hoạch tương đương của Sql Server đôi khi cũng gọi một thứ quét liên quan đến tìm kiếm và đôi khi nó gọi đó là tìm kiếm. Tôi luôn cần nhìn vào số hàng để hiểu những gì. Vì rõ ràng có một 3334 trong đầu ra của db2, nó chắc chắn làm những gì tôi đã hy vọng. Rất thú vị.
Giăng

Vâng, đôi khi tôi cũng thấy khó hiểu. Người ta phải xem thông tin chi tiết hơn cho mỗi nhà khai thác để thực sự hiểu những gì đang xảy ra.
Lennart

0

Trong truy vấn cụ thể này, thật là ngớ ngẩn khi thậm chí có một CASEtuyên bố. Bạn đang lọc xuống một trường hợp cụ thể! Có lẽ đây chỉ là một chi tiết của truy vấn ví dụ cụ thể mà bạn đã đưa ra, nhưng nếu không, bạn có thể viết truy vấn này để có kết quả tương đương:

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

Điều này sẽ cung cấp cho bạn chính xác cùng một tập kết quả và vì dù sao bạn cũng đã cứng các giá trị mã hóa trong một CASEtuyên bố, nên bạn sẽ không mất bất kỳ khả năng bảo trì nào ở đây.


1
Tôi nghĩ rằng bạn đang thiếu điểm ba, đây là SQL được tạo từ một cơ sở mã phía sau hoạt động với enum thông qua các biểu diễn chuỗi của chúng. Mã đang chiếu SQL đang thực hiện bạo lực đối với truy vấn. Tôi chắc chắn rằng người hỏi, nếu anh ta tự viết SQL, sẽ có thể viết một truy vấn tốt hơn. Do đó, không có gì ngớ ngẩn khi có một CASEtuyên bố nào cả, bởi vì các ORM làm điều đó. Điều ngớ ngẩn là bạn đã không nhận ra những khía cạnh đơn giản của vấn đề này ... (làm thế nào mà nó được gọi gián tiếp là vô não?)
ErikE

@ErikE Vẫn hơi ngớ ngẩn, vì bạn chỉ có thể sử dụng giá trị số của enum, giả sử C # nào. (Một giả định khá an toàn được đưa ra là chúng ta đang nói về SQL Server.)
jpmc26

Nhưng bạn không biết trường hợp sử dụng là gì. Có lẽ nó sẽ là một thay đổi lớn để chuyển sang giá trị số. Có lẽ các enum đã được trang bị thêm vào một cơ sở mã khổng lồ hiện có. Phê bình mà không có kiến ​​thức là vô lý.
ErikE

@ErikE Nếu nó vô lý, thì tại sao bạn làm điều đó? =) Tôi chỉ trả lời để chỉ ra rằng nếu trường hợp sử dụng đơn giản như ví dụ trong câu hỏi (được quy định rõ ràng trong lời nói đầu của câu trả lời của tôi), thì CASEcâu lệnh có thể được loại bỏ hoàn toàn mà không có nhược điểm. Tất nhiên có thể có các yếu tố không xác định, nhưng chúng không được chỉ định.
jpmc26

Tôi không phản đối các phần thực tế trong câu trả lời của bạn, chỉ là những phần đặc trưng chủ quan. Về việc tôi đang chỉ trích mà không có kiến ​​thức, tôi đều có thể hiểu bất kỳ cách nào mà tôi đã thất bại trong việc sử dụng logic sạch một cách
thô lỗ
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.