Cách tối ưu hóa truy vấn


9

Tôi có một cấu trúc cơ sở dữ liệu tương tự như thế này,

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

Điểm của bảng DispatchLink là liên kết hai bản ghi Dispatch với nhau. Nhân tiện, tôi đang sử dụng khóa chính tổng hợp trên bảng công văn của mình vì tính kế thừa, vì vậy tôi không thể thay đổi điều đó mà không phải chịu nhiều đau đớn. Ngoài ra bảng liên kết có thể không phải là cách chính xác để làm điều đó? Nhưng một lần nữa di sản.

Vì vậy, câu hỏi của tôi, nếu tôi chạy truy vấn này

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

Tôi không bao giờ có thể lấy nó để thực hiện tìm kiếm chỉ mục trên bảng DispatchLink. Nó luôn luôn quét toàn bộ chỉ mục. Điều đó là tốt với một vài bản ghi, nhưng khi bạn có 50000 trong bảng đó, nó sẽ quét 50000 bản ghi trong chỉ mục theo kế hoạch truy vấn. Đó là bởi vì có 'ands' và 'ors' trong mệnh đề nối, nhưng tôi không thể hiểu được lý do tại sao SQL không thể thực hiện một vài chỉ mục tìm kiếm, thay vào đó là một bên trái của 'hoặc', và một cho bên phải của 'hoặc'.

Tôi muốn một lời giải thích cho điều này, không phải là một gợi ý để làm cho truy vấn nhanh hơn trừ khi điều đó có thể được thực hiện mà không cần điều chỉnh truy vấn. Lý do là tôi đang sử dụng truy vấn trên làm bộ lọc kết hợp sao chép hợp nhất, vì vậy tôi không thể chỉ thêm vào một loại truy vấn khác.

CẬP NHẬT: Ví dụ, đây là các loại chỉ mục tôi đã thêm,

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

Vì vậy, nó sử dụng các chỉ mục, nhưng thực hiện quét chỉ mục trên toàn bộ chỉ mục, vì vậy 50000 bản ghi nó quét 50000 bản ghi trong chỉ mục.


Bạn có chỉ số nào trên DispatchLinkbàn không?
ypercubeᵀᴹ

Tôi đã thêm các chỉ mục tôi đã thử ở trên.
peter

Trong truy vấn của bạn: "select * từ Dispatch d bên trong tham gia DispatchLink dl trên d.DispatchId = dl.DispatchLink1 và d.ContractId = dl.ContractLink1 hoặc d.DispatchId = dl.DispatchLink2 và d.ContractId = dl. điều kiện "HOẶC" và thay thế nó bằng UNION gồm 2 câu lệnh CHỌN mỗi câu không sử dụng "OR", cũng sử dụng các cột khóa duy nhất trong cả hai CHỌN thay vì "*", để làm cho phép thử hoàn toàn nhất có thể.
NoChance

Cảm ơn SQL Kiwi, đây là thứ tôi đã thử trước đây nhưng nó không hoạt động.
peter

1
Bạn có thể có vấn đề sao chép một truy vấn đơn giản hơn không: chọn * từ Dispatch d bên trong tham gia DispatchLink dl trên d.DispatchId = dl.DispatchLink1 và d.ContractId = dl.ContractLink1 Nếu có, chúng tôi có thể sao chép dữ liệu trong DispatchLink để kết quả vẫn hợp lệ ...
AK

Câu trả lời:


12

Trình tối ưu hóa có thể xem xét nhiều lựa chọn thay thế kế hoạch (bao gồm cả các lựa chọn có nhiều tìm kiếm) nhưng đối với các bất đồng ( ORvị từ), nó không xem xét các kế hoạch liên quan đến giao điểm chỉ mục theo mặc định. Đưa ra các chỉ số:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

Chúng tôi có thể buộc tìm kiếm chỉ mục (giả sử SQL Server 2008 trở lên):

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Kế hoạch FORCESEEK

Sử dụng dữ liệu mẫu của bạn, kế hoạch tìm kiếm có chi phí ở mức 0,032551 đơn vị so với 0,0068057 cho kế hoạch quét:

Kế hoạch quét

Có tất cả các loại viết lại truy vấn có thể và gợi ý chúng ta có thể thử. Một ví dụ về viết lại để thúc đẩy một tùy chọn mà trình tối ưu hóa không xem xét cho kế hoạch ban đầu là:

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Kế hoạch thực hiện này không tìm kiếm chỉ mục thứ hai nếu nó tìm thấy sự trùng khớp ở lần đầu tiên:

ÁP DỤNG Kế hoạch hàng đầu

Điều này có thể thực hiện tốt hơn một chút so với FORCESEEKkế hoạch mặc định .

Không cần thêm bất kỳ chỉ mục mới nào, chúng tôi cũng có thể buộc tìm kiếm vào bảng Công văn:

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Tìm kiếm 2

Điều này có thể tốt hơn hoặc xấu hơn ví dụ đầu tiên tùy thuộc vào những thứ như có bao nhiêu hàng trong mỗi bảng. Sự APPLY + TOPcải thiện vẫn có thể:

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Đó là một câu trả lời rất hữu ích. Tôi đã hỏi một câu hỏi khác dba.stackexchange.com/questions/23773/analysing-a-query-plan trong đó hiển thị kế hoạch truy vấn thực tế trên dữ liệu thực (không phải dữ liệu thử nghiệm của tôi). Tôi không có kiến ​​thức để hiểu chính xác nút thắt trên kế hoạch truy vấn là gì. Có lẽ bạn có thể xem?
peter

Điều đó thực sự thú vị bởi vì việc thêm 'FORCESEEK' làm cho truy vấn của tôi chạy trong 9 giây thay vì mất hơn 10 phút. Cập nhật số liệu thống kê làm cho không có sự khác biệt. Tại sao người phân tích truy vấn khác lại hiểu sai như vậy?
peter

Tôi nghĩ bạn đúng về thiết kế. Bạn có ý nghĩa gì về việc lặp lại các cột? Làm thế nào bạn sẽ thiết kế một cấu trúc bảng sẽ phải liên kết hai bản ghi công văn với nhau như có liên quan? Để làm rõ mặc dù bảng 'thực' có trường khóa chính của riêng nó, nhưng vâng, có một khóa tổng hợp trong Dispatch không giúp chính xác.
peter

Kiwi SQL. Lặp đi lặp lại cột. Hiểu rồi, cảm ơn.
peter
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.