Các truy vấn rất giống nhau, hiệu suất rất khác nhau


9

Tôi có hai truy vấn rất giống nhau

Truy vấn đầu tiên:

SELECT count(*)
FROM Audits a
    JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
    and a.TargetTypeId IN 
    (1,2,3,4,5,6,7,8,9,
    11,12,13,14,15,16,17,18,19,
    21,22,23,24,25,26,27,28,29,30,
    31,32,33,34,35,36,37,38,39,
    41,42,43,44,45,46,47,48,49,
    51,52,53,54,55,56,57,58,59,
    61,62,63,64,65,66,67,68,69,
    71,72,73,74,75,76,77,78,79)

Kết quả: 267479

Gói: https://www.brentozar.com/pastetheplan/?id=BJWTtILyS


Truy vấn thứ hai:

SELECT count(*)
FROM Audits a
    JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
    and a.TargetTypeId IN 
    (1,2,3,4,5,6,7,8,9,
    11,12,13,14,15,16,17,18,19,
    21,22,23,24,25,26,27,28,29,
    31,32,33,34,35,36,37,38,39,
    41,42,43,44,45,46,47,48,49,
    51,52,53,54,55,56,57,58,59,
    61,62,63,64,65,66,67,68,69,
    71,72,73,74,75,76,77,78,79)

Kết quả: 25650

Gói: https://www.brentozar.com/pastetheplan/?id=S1v79U8kS


Truy vấn đầu tiên mất khoảng một giây để hoàn thành, trong khi truy vấn thứ hai mất khoảng 20 giây. Điều này hoàn toàn trái ngược với tôi vì truy vấn đầu tiên có số lượng cao hơn nhiều so với truy vấn thứ hai. Đây là trên máy chủ SQL 2012

Tại sao có quá nhiều sự khác biệt? Làm thế nào tôi có thể tăng tốc truy vấn thứ hai nhanh như truy vấn thứ nhất?


Đây là kịch bản Tạo bảng cho cả hai bảng:

CREATE TABLE [dbo].[AuditRelatedIds](
    [AuditId] [bigint] NOT NULL,
    [RelatedId] [uniqueidentifier] NOT NULL,
    [AuditTargetTypeId] [smallint] NOT NULL,
 CONSTRAINT [PK_AuditRelatedIds] PRIMARY KEY CLUSTERED 
(
    [AuditId] ASC,
    [RelatedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_INCLUDES] ON [dbo].[AuditRelatedIds]
(
    [RelatedId] ASC
)
INCLUDE (   [AuditId]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

ALTER TABLE [dbo].[AuditRelatedIds]  WITH CHECK ADD  CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id] FOREIGN KEY([AuditId])
REFERENCES [dbo].[Audits] ([Id])

ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditId_Audits_Id]

ALTER TABLE [dbo].[AuditRelatedIds]  WITH CHECK ADD  CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([AuditTargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])

ALTER TABLE [dbo].[AuditRelatedIds] CHECK CONSTRAINT [FK_AuditRelatedIds_AuditTargetTypeId_AuditTargetTypes_Id]

CREATE TABLE [dbo].[Audits](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [TargetTypeId] [smallint] NOT NULL,
    [TargetId] [nvarchar](40) NOT NULL,
    [TargetName] [nvarchar](max) NOT NULL,
    [Action] [tinyint] NOT NULL,
    [ActionOverride] [tinyint] NULL,
    [Date] [datetime] NOT NULL,
    [UserDisplayName] [nvarchar](max) NOT NULL,
    [DescriptionData] [nvarchar](max) NULL,
    [IsNotification] [bit] NOT NULL,
 CONSTRAINT [PK_Audits] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

SET ANSI_PADDING ON

CREATE NONCLUSTERED INDEX [IX_AuditsTargetId] ON [dbo].[Audits]
(
    [TargetId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

SET ANSI_PADDING ON

CREATE NONCLUSTERED INDEX [IX_AuditsTargetTypeIdAction_INCLUDES] ON [dbo].[Audits]
(
    [TargetTypeId] ASC,
    [Action] ASC
)
INCLUDE (   [TargetId],
    [UserDisplayName]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 100) ON [PRIMARY]

ALTER TABLE [dbo].[Audits]  WITH CHECK ADD  CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id] FOREIGN KEY([TargetTypeId])
REFERENCES [dbo].[AuditTargetTypes] ([Id])

ALTER TABLE [dbo].[Audits] CHECK CONSTRAINT [FK_Audits_TargetTypeId_AuditTargetTypes_Id]

3
Chúng ta có thể có được một số lược đồ bảng và chi tiết chỉ mục. Như tôi chắc chắn rằng bạn nhận thấy các kế hoạch có một chút khác biệt nhưng rõ ràng nó đang tạo ra một sự khác biệt lớn. Nếu chúng ta có thể có được những chi tiết đó hơn có lẽ chúng ta có thể thấy những tùy chọn chúng ta có.
Kirk Saunders

2
Là một mẹo rất nhanh, thay vì sử dụng IN, hãy tạo một TempTable với một cột TINYINT / INT (được nhóm) với các số bạn muốn và sau đó INNER THAM GIA với nó. Ngoài ra, chúng tôi có thể sẽ cần thông tin DDL như @KirkSaunders đã đề cập ở trên
George.Palacios

2
Có điều gì đặc biệt TargetTypeId = 30không? Có vẻ các kế hoạch là khác nhau bởi vì một giá trị này thực sự sai lệch lượng dữ liệu (dự kiến ​​sẽ được) trả lại.
Aaron Bertrand

Tôi nhận ra rằng nó rất tệ về mặt mô phạm nhưng câu lệnh "truy vấn đầu tiên trả về nhiều hàng hơn so với lần thứ hai". không đúng Cả hai trả về 1 hàng;)
ypercubeᵀᴹ

1
Tôi đã cập nhật câu hỏi với các câu lệnh tạo bảng cho cả hai bảng
Chocoman

Câu trả lời:


8

Tl; dr ở phía dưới

Tại sao kế hoạch xấu được chọn

Lý do chính để chọn một kế hoạch so với kế hoạch khác là Estimated total subtreechi phí.

Chi phí này thấp hơn cho kế hoạch xấu so với kế hoạch thực hiện tốt hơn.

Tổng chi phí ước tính cho kế hoạch xấu:

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

Tổng chi phí cây con ước tính cho kế hoạch thực hiện tốt hơn của bạn

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


Nhà điều hành ước tính chi phí

Một số nhà khai thác có thể mất phần lớn chi phí này và có thể là một lý do để trình tối ưu hóa chọn một đường dẫn / kế hoạch khác.

Trong kế hoạch thực hiện tốt hơn của chúng tôi, phần lớn Subtreecostđược tính trên index seek& nested loops operatorthực hiện tham gia:

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

Trong khi đối với gói truy vấn xấu của chúng tôi, Clustered index seekchi phí vận hành thấp hơn

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

Mà nên giải thích tại sao kế hoạch khác có thể đã được chọn.

(Và bằng cách thêm tham số 30làm tăng chi phí của kế hoạch xấu, nơi nó đã tăng cao hơn 871.510000chi phí ước tính). Dự đoán ™

Kế hoạch thực hiện tốt hơn

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

Kế hoạch xấu

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


Điều này đưa chúng ta đến đâu?

Thông tin này đưa chúng ta đến một cách để buộc kế hoạch truy vấn xấu trong ví dụ của chúng tôi (Xem DML để sao chép gần như vấn đề của OP đối với dữ liệu được sử dụng để sao chép vấn đề)

Bằng cách thêm một INNER LOOP JOINgợi ý tham gia

SELECT count(*)
FROM Audits a
   INNER LOOP JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
    and a.TargetTypeId IN 
    (1,2,3,4,5,6,7,8,9,
    11,12,13,14,15,16,17,18,19,
    21,22,23,24,25,26,27,28,29,
    31,32,33,34,35,36,37,38,39,
    41,42,43,44,45,46,47,48,49,
    51,52,53,54,55,56,57,58,59,
    61,62,63,64,65,66,67,68,69,
    71,72,73,74,75,76,77,78,79)

Nó gần hơn, nhưng có một số khác biệt thứ tự tham gia:

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


Viết lại

Thay vào đó, lần thử viết lại đầu tiên của tôi có thể lưu trữ tất cả các số này trong bảng tạm thời:

CREATE TABLE #Numbers(Numbering INT)
INSERT INTO #Numbers(Numbering)
VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(11),(12),(13),(14),(15),(16),(17),(18),(19),
(21),(22),(23),(24),(25),(26),(27),(28),(29),(30),(31),(32),(33),(34),(35),
(36),(37),(38),(39),(41),(42),(43),(44),(45),(46),(47),(48),(49),(51),(52),
(53),(54),(55),(56),(57),(58),(59),(61),(62),(63),(64),(65),(66),(67),(68),
(69),(71),(72),(73),(74),(75),(76),(77),(78),(79);

Và sau đó thêm một JOINthay vì lớnIN()

SELECT count(*)
FROM Audits a
   INNER LOOP JOIN AuditRelatedIds ari ON a.Id = ari.AuditId
   INNER JOIN #Numbers
   ON Numbering = a.TargetTypeId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1';

Gói truy vấn của chúng tôi khác nhau nhưng chưa được sửa:

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

với chi phí vận hành ước tính rất lớn trên AuditRelatedIdsbàn

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


Đây là nơi tôi nhận thấy rằng

Lý do mà tôi không thể trực tiếp tạo lại gói của bạn là lọc bitmap được tối ưu hóa.

Tôi có thể tạo lại kế hoạch của bạn bằng cách vô hiệu hóa các bộ lọc bitmap được tối ưu hóa bằng cách sử dụng dấu vết 7497&7498

SELECT count(*)
FROM Audits a 
   INNER JOIN AuditRelatedIds  ari ON a.Id = ari.AuditId 
   INNER JOIN #Numbers
   ON Numbering = a.TargetTypeId
WHERE 
    ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'
OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498);

Thông tin thêm về các bộ lọc bitmap được tối ưu hóa ở đây .

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

Điều này có nghĩa là, nếu không có các bộ lọc bitmap, trình tối ưu hóa cho rằng tốt hơn là trước tiên hãy tham gia vào #numberbảng và sau đó tham gia vào AuditRelatedIdsbảng.

Khi buộc đơn hàng OPTION (QUERYTRACEON 7497, QUERYTRACEON 7498, FORCE ORDER);chúng ta có thể thấy tại sao:

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

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

Không tốt


Loại bỏ khả năng đi song song với maxdop 1

Khi thêm MAXDOP 1truy vấn thực hiện nhanh hơn, luồng đơn.

Và thêm chỉ số này

CREATE NONCLUSTERED INDEX [IX_AuditRelatedIdsRelatedId_AuditId] ON [dbo].[AuditRelatedIds]
(
    [RelatedId] ASC,
    [AuditId] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY];

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

Trong khi sử dụng một liên kết hợp nhất. nhập mô tả hình ảnh ở đây

Điều tương tự cũng đúng khi chúng tôi xóa gợi ý truy vấn thứ tự lực lượng hoặc không sử dụng bảng #Numbers và sử dụng IN()thay thế.

Lời khuyên của tôi sẽ là xem xét thêm MAXDOP(1)và xem nếu điều đó giúp ích cho truy vấn của bạn, với việc viết lại nếu cần.

Tất nhiên, bạn cũng nên nhớ rằng về phần cuối, nó hoạt động tốt hơn do lọc bitmap được tối ưu hóa & thực sự sử dụng nhiều luồng để có hiệu quả tốt:

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

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


TL; DR

Chi phí ước tính sẽ xác định kế hoạch đã chọn, tôi có thể sao chép hành vi và thấy rằng các toán tử optimized bitmap filters+ parallellismđược thêm vào cuối của tôi để thực hiện truy vấn một cách nhanh chóng và hiệu quả.

Bạn có thể xem xét thêm MAXDOP(1)vào truy vấn của mình như một cách để hy vọng có được kết quả được kiểm soát tương tự mỗi lần, với một merge joinvà không "xấu" parallellism.

Nâng cấp lên phiên bản mới hơn và sử dụng phiên bản ước tính cardinality cao hơn CardinalityEstimationModelVersion="70"cũng có thể giúp ích.

Một bảng tạm thời số để thực hiện lọc đa giá trị cũng có thể giúp đỡ.


DML gần như sao chép vấn đề của OP

Tôi đã dành nhiều thời gian hơn cho việc này hơn là tôi muốn thừa nhận

set NOCOUNT ON;
DECLARE @I INT = 0
WHILE @I < 56
BEGIN
INSERT INTO  [dbo].[Audits] WITH(TABLOCK) 
([TargetTypeId],
    [TargetId],
    [TargetName],
    [Action],
    [ActionOverride] ,
    [Date] ,
    [UserDisplayName],
    [DescriptionData],
    [IsNotification]) 
SELECT top(500000) CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 10000 END as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;
SET @I +=1;
END

-- 'Bad Query matches'
INSERT INTO  [dbo].[AuditRelatedIds] WITH(TABLOCK)
    ([AuditId] ,
    [RelatedId]  ,
    [AuditTargetTypeId])
SELECT
TOP(25650)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1, 
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') , 
CASE WHEN ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 = 30 then 29 ELSE ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) / 510 END as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2

-- Extra matches with 30
SELECT MAX([Id]) FROM [dbo].[Audits];
--28000001 Upper value

INSERT INTO  [dbo].[Audits] WITH(TABLOCK) 
([TargetTypeId],
    [TargetId],
    [TargetName],
    [Action],
    [ActionOverride] ,
    [Date] ,
    [UserDisplayName],
    [DescriptionData],
    [IsNotification]) 
SELECT top(241829) 30 as rownum2 -- TILL 50 and no 30
,'bla','bla2',1,1,getdate(),'bla3','Bla4',1
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2;



;WITH CTE AS
(SELECT
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as rownum1, 
('1DD87CF1-286B-409A-8C60-3FFEC394FDB1') as gu , 
30 as rownum2 -- TILL 50 and no 30
FROM master.dbo.spt_values spt1
CROSS APPLY master.dbo.spt_values spt2
CROSS APPLY master.dbo.spt_values spt3
)
--267479 - 25650 = 241829
INSERT INTO  [dbo].[AuditRelatedIds] WITH(TABLOCK)
    ([AuditId] ,
    [RelatedId]  ,
    [AuditTargetTypeId])

SELECT TOP(241829) rownum1,gu,rownum2 FROM CTE
WHERE rownum1 > 28000001
ORDER BY rownum1 ASC;

Giải thích rất hay! Thêm MAXDOP 0dường như đã sửa nó. Cảm ơn rât nhiều!
Chocoman

1
MAXDOP 1 ** (lỗi đánh máy)
Chocoman

@Chocoman Tuyệt vời! Rất vui được giúp đỡ :)
Randi Vertongen

1

Từ những gì tôi có thể nói sự khác biệt chính giữa hai kế hoạch là sự khác biệt trong "Bộ lọc chính" là gì.

Với phiên bản đầu tiên, bộ lọc chính đã được tạo ra có Audit.IDliên quan đến ari.RelatedId = '1DD87CF1-286B-409A-8C60-3FFEC394FDB1'bộ lọc đó sau đó lọc ra những người Audit.TargetTypeIDtrong danh sách.

Với phiên bản thứ hai, bộ lọc chính đã được tạo ra có Audit.IDliên quan đến danh sách Audit.TargetTypeID.

Kể từ khi bổ sung Audit.TargetTypeID = 30xuất hiện để tăng đáng kể số lượng hồ sơ (lần lượt là 267.479 và 25.650 theo Câu hỏi gốc). Đó có lẽ là lý do tại sao các kế hoạch thực hiện là khác nhau. (Theo tôi hiểu) SQL sẽ cố gắng thực hiện chức năng chọn lọc nhất trước và sau đó áp dụng các quy tắc còn lại sau đó. Với phiên bản đầu tiên, truy vấn bằng cách AuditRelatedID.RelatedIDtìm sau đó Audit.IDcó thể được lựa chọn nhiều hơn là cố gắng sử dụng Audit.TargetTypeIDđể sau đó tìm Audit.ID.

Để tín dụng của ypercube. Bạn chắc chắn có thể cập nhật [AuditRelatedIds].[IX_AuditRelatedIdsRelatedId_INCLUDES]để có cả hai RelatedIDAuditIDlà một phần của chỉ mục thay vì có AuditIDmột phần của một INCLUDE. Nó không chiếm bất kỳ không gian chỉ mục bổ sung nào và sẽ cho phép bạn sử dụng cả hai cột trong JOINmệnh đề. Điều đó có thể giúp Trình tối ưu hóa truy vấn tạo cùng một kế hoạch thực hiện cho cả hai truy vấn.

Hoạt động với một logic tương tự, có thể có một số lợi ích đối với một chỉ mục Auditchứa TargetTypeID ASC, ID ASCcác nút lọc / thứ tự thực tế (không phải là một phần của INCLUDE). Điều này sẽ cho phép trình tối ưu hóa Truy vấn lọc Audit.TargetTypeIDsau đó nhanh chóng tham gia AuditReferenceIds.AuditID. Bây giờ điều này có thể kết thúc với cả hai truy vấn chọn kế hoạch kém hiệu quả hơn nên tôi sẽ chỉ thực hiện sau khi thử khuyến nghị của ypercube.

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.