SQL Server: Truy vấn nhanh, nhưng chậm từ thủ tục


257

Một truy vấn chạy nhanh:

DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'

SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

chi phí phụ: 0,52

Nhưng việc đặt cùng một SQL trong một thủ tục được lưu trữ sẽ chạy chậm và với một kế hoạch thực hiện hoàn toàn khác

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

EXECUTE ViewOpener @SessionGUID

Chi phí phụ: 19.2

Tôi đã chạy

sp_recompile ViewOpener

Và nó vẫn chạy tương tự (rất tệ) và tôi cũng đã thay đổi quy trình được lưu trữ thành

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *, 'recompile please'
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

Và trở lại một lần nữa, cố gắng thực sự lừa nó biên dịch lại.

Tôi đã bỏ và tạo lại thủ tục được lưu trữ để làm cho nó tạo ra một kế hoạch mới.

Tôi đã thử buộc biên dịch lại và ngăn chặn việc đánh hơi tham số , bằng cách sử dụng biến decoy:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS

DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID

SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUIDbitch
ORDER BY CurrencyTypeOrder, Rank

Tôi cũng đã thử xác định thủ tục được lưu trữ WITH RECOMPILE:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier 
WITH RECOMPILE
AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

Vì vậy, kế hoạch của nó không bao giờ được lưu trong bộ nhớ cache và tôi đã thử buộc biên dịch lại khi thực thi:

EXECUTE ViewOpener @SessionGUID WITH RECOMPILE

Mà không giúp được gì.

Tôi đã thử chuyển đổi thủ tục sang SQL động:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier 
WITH RECOMPILE AS
DECLARE @SQLString NVARCHAR(500)

SET @SQLString = N'SELECT *
   FROM Report_OpenerTest
   WHERE SessionGUID = @SessionGUID
   ORDER BY CurrencyTypeOrder, Rank'

EXECUTE sp_executesql @SQLString,
N'@SessionGUID uniqueidentifier',
@SessionGUID

Mà không giúp được gì.

Thực thể " Report_Opener" là một khung nhìn, không được lập chỉ mục. Khung nhìn chỉ tham chiếu các bảng bên dưới. Không có bảng nào chứa các cột được tính toán, được lập chỉ mục hoặc cách khác.

Đối với địa ngục của nó, tôi đã cố gắng tạo ra chế độ xem với

SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON

Điều đó đã không sửa chữa nó.

Nó thế nào

  • truy vấn nhanh
  • di chuyển truy vấn sang chế độ xem và chọn từ chế độ xem nhanh
  • chọn từ chế độ xem từ một thủ tục được lưu trữ là chậm hơn 40 lần?

Tôi đã thử chuyển định nghĩa của khung nhìn trực tiếp vào thủ tục được lưu trữ (vi phạm 3 quy tắc kinh doanh và phá vỡ một đóng gói quan trọng), và điều đó làm cho nó chỉ chậm hơn khoảng 6 lần.

Tại sao phiên bản thủ tục được lưu trữ rất chậm? Điều gì có thể giải thích cho SQL Server chạy SQL ad-hoc nhanh hơn một loại SQL ad-hoc khác?

Tôi thực sự không thích

  • nhúng SQL vào mã
  • thay đổi mã

    Microsoft SQL Server  2000 - 8.00.2050 (Intel X86)
    Mar  7 2008 21:29:56
    Copyright (c) 1988-2003 Microsoft Corporation
    Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
    

Nhưng những gì có thể giải thích cho SQL Server không thể chạy nhanh như SQL Sever đang chạy truy vấn, nếu không phải là thông số đánh hơi.


Nỗ lực tiếp theo của tôi sẽ là có StoredProcedureAcuộc StoredProcedureBgọi StoredProcedureCcuộc gọi StoredProcedureDđể truy vấn xem.

Và không thành công, có một thủ tục được lưu trữ gọi một thủ tục được lưu trữ, gọi UDF, gọi UDF, gọi một thủ tục được lưu trữ, gọi UDF để truy vấn khung nhìn.


Tóm lại, những điều sau đây chạy nhanh từ QA, nhưng chậm khi đưa vào một thủ tục được lưu trữ:

Bản gốc:

--Runs fine outside of a stored procedure
SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

sp_executesql:

--Runs fine outside of a stored procedure
DECLARE @SQLString NVARCHAR(500)
SET @SQLString = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank'

EXECUTE sp_executesql @SQLString,
        N'@SessionGUID uniqueidentifier',
        @SessionGUID

EXEC(@sql):

--Runs fine outside of a stored procedure
DECLARE @sql NVARCHAR(500)
SET @sql = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = '''+CAST(@SessionGUID AS varchar(50))+'''
ORDER BY CurrencyTypeOrder, Rank'

EXEC(@sql)

Kế hoạch thực hiện

Các tốt kế hoạch:

      |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
           |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[CurrencyType]
                |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
                     |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currencies].
                     |    |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
                     |         |--Nested Loops(Left Outer Join)
                     |         |    |--Bookmark Lookup(BOOKMARK:([Bmk1016]), OBJECT:([GrobManagementSystemLive].[dbo].[Windows]))
                     |         |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([Openers].[WindowGUID]))
                     |         |    |         |--Bookmark Lookup(BOOKMARK:([Bmk1014]), OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
                     |         |    |         |    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_SessionGUID]), SEEK:([Openers].[SessionGUID]=[@SessionGUID]) ORDERED FORWARD)
                     |         |    |         |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows]), SEEK:([Windows].[WindowGUID]=[Openers].[WindowGUID]) ORDERED FORWARD)
                     |         |    |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                     |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Currenc
                     |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
                          |--Stream Aggregate(DEFINE:([Expr1006]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='ctCanadianCoin') OR [
                               |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
                                    |--Nested Loops(Inner Join)
                                    |    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                    |    |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)

Các xấu kế hoạch

       |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
            |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[Currency
                 |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
                      |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currenc
                      |    |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
                      |         |--Filter(WHERE:([Openers].[SessionGUID]=[@SessionGUID]))
                      |         |    |--Concatenation
                      |         |         |--Nested Loops(Left Outer Join)
                      |         |         |    |--Table Spool
                      |         |         |    |    |--Hash Match(Inner Join, HASH:([Windows].[WindowGUID])=([Openers].[WindowGUID]), RESIDUAL:([Windows].[WindowGUID]=[Openers].[WindowGUID]))
                      |         |         |    |         |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows_CageGUID]))
                      |         |         |    |         |--Table Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
                      |         |         |    |--Table Spool
                      |         |         |         |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                      |         |         |--Compute Scalar(DEFINE:([Openers].[OpenerGUID]=NULL, [Openers].[SessionGUID]=NULL, [Windows].[UseChipDenominations]=NULL))
                      |         |              |--Nested Loops(Left Anti Semi Join)
                      |         |                   |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                      |         |                   |--Row Count Spool
                      |         |                        |--Table Spool
                      |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Cu
                      |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
                           |--Stream Aggregate(DEFINE:([Expr1006]=SUM([partialagg1034]), [Expr1007]=SUM([partialagg1035]), [Expr1008]=SUM([partialagg1036]), [Expr1009]=SUM([partialagg1037]), [Expr1010]=SUM([partialagg1038]), [Expr1011]=SUM([partialagg1039]
                                |--Nested Loops(Inner Join)
                                     |--Stream Aggregate(DEFINE:([partialagg1034]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='
                                     |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
                                     |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                     |         |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)
                                     |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)

Người xấu đang háo hức với 6 triệu hàng; một cái khác thì không.

Lưu ý: Đây không phải là câu hỏi về điều chỉnh truy vấn. Tôi có một truy vấn chạy nhanh như chớp. Tôi chỉ muốn SQL Server chạy nhanh từ một thủ tục được lưu trữ.


Tôi nhận thấy mỗi khi bạn lấy một tham số và gán lại nó cho một tham số khác và sau đó sử dụng nó trong một truy vấn thì điều này có thể xảy ra và như câu trả lời cho thấy Tối ưu hóa cho @ "someparamname" không biết có thể hoạt động.
JustDave

Câu trả lời:


404

Tôi đã có cùng một vấn đề như người đăng ban đầu nhưng câu trả lời được trích dẫn không giải quyết được vấn đề cho tôi. Truy vấn vẫn chạy rất chậm từ một thủ tục được lưu trữ.

Tôi tìm thấy một câu trả lời khác ở đây "Thông số đánh hơi" , Cảm ơn Omnibuzz. Sử dụng "Biến cục bộ" trong các truy vấn thủ tục được lưu trữ của bạn, nhưng đọc bản gốc để hiểu rõ hơn, đó là một bài viết tuyệt vời. ví dụ

Cách chậm:

CREATE PROCEDURE GetOrderForCustomers(@CustID varchar(20))
AS
BEGIN
    SELECT * 
    FROM orders
    WHERE customerid = @CustID
END

Cách nhanh chóng:

CREATE PROCEDURE GetOrderForCustomersWithoutPS(@CustID varchar(20))
AS
BEGIN
    DECLARE @LocCustID varchar(20)
    SET @LocCustID = @CustID

    SELECT * 
    FROM orders
    WHERE customerid = @LocCustID
END

Hy vọng điều này sẽ giúp người khác, làm điều này giúp giảm thời gian thực hiện của tôi từ hơn 5 phút xuống còn khoảng 6-7 giây.


23
+1 Nhưng, điều này rất lạ, và đặt ra rất nhiều câu hỏi như chúng ta nên làm điều này cho tất cả các thủ tục và nếu không, khi nào nên làm điều này?
gotqn

31
Tôi có phải là người duy nhất bị cản trở bởi hành vi này không ?? Yêu cầu các biến cục bộ được khai báo để ngăn chặn tham số đánh hơi ?? Không phải SQL Server đủ thông minh để ngăn điều này xảy ra ngay từ đầu sao? Điều này chỉ gây ra sự phình to mã không cần thiết bởi thiết kế thiển cận của Microsoft IMHO.
l46kok

4
15 phút -> 8 giây! cứu người
Tony Brix

3
@BennettDill WITH RECOMPILEkhông tạo ra sự khác biệt cho tôi, chỉ có các tham số cục bộ.
nhạo báng

8
Bây giờ có thể đạt được điều này bằng cách sử dụng gợi ý truy vấn - TÙY CHỌN (TỐI ƯU HÓA CHO (@varA UNKNOWN, @varB UNKNOWN)
Dave

131

Tôi đã tìm thấy sự cố, đây là tập lệnh của các phiên bản chậm và nhanh của quy trình được lưu trữ:

dbo.ViewOpener__RenamedForCruachan__Slow.PRC

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS OFF 
GO

CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Slow
    @SessionGUID uniqueidentifier
AS

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

dbo.ViewOpener__RenamedForCruachan__Fast.PRC

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Fast
    @SessionGUID uniqueidentifier 
AS

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

Nếu bạn không phát hiện ra sự khác biệt, tôi không đổ lỗi cho bạn. Sự khác biệt không nằm trong thủ tục lưu trữ. Sự khác biệt biến một truy vấn nhanh 0,5 chi phí thành một truy vấn háo hức gồm 6 triệu hàng:

Chậm: SET ANSI_NULLS OFF

Nhanh: SET ANSI_NULLS ON


Câu trả lời này cũng có thể được thực hiện để có ý nghĩa, vì chế độ xem có mệnh đề tham gia có nội dung:

(table.column IS NOT NULL)

Vì vậy, có một số NULLs liên quan.


Lời giải thích được chứng minh thêm bằng cách quay lại Trình phân tích truy vấn và chạy

SET ANSI_NULLS OFF

.

DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'

.

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

Và truy vấn chậm.


Vì vậy, vấn đề không phải là do truy vấn đang được chạy từ một thủ tục được lưu trữ. Vấn đề là tùy chọn mặc định kết nối của Trình quản lý doanh nghiệp ANSI_NULLS off, chứ không phải ANSI_NULLS onlà mặc định của QA.

Microsoft thừa nhận thực tế này trong KB296769 (BUG: Không thể sử dụng SQL Enterprise Manager để tạo các thủ tục được lưu trữ có chứa các đối tượng máy chủ được liên kết). Cách giải quyết bao gồm ANSI_NULLStùy chọn trong hộp thoại thủ tục được lưu trữ:

Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....

2
Tôi vẫn không hiểu làm thế nào biến ANSI_NULLS ONtạo ra một sự khác biệt hiệu suất lớn như vậy.
Justin Helgerson

2
@ Ek0nomik Vì các JOINmệnh đề bên trong khung nhìn có ý nghĩa khác nhau khi ANSI_NULLS OFF. Đột nhiên các hàng khớp nhau, làm cho trình tối ưu hóa chạy truy vấn hoàn toàn khác nhau. Hãy tưởng tượng rằng thay vì loại bỏ 99,9% tất cả các hàng, chúng đột nhiên quay trở lại.
Ian Boyd

2
Lưu ý: ANSI_NULLS OFFkhông được dùng nữa và bị coi là một thói quen xấu
jean

2
link "Trong phiên bản tương lai của SQL Server, ANSI_NULLS sẽ luôn BẬT và mọi ứng dụng đặt tùy chọn TẮT rõ ràng sẽ gây ra lỗi. Tránh sử dụng tính năng này trong công việc phát triển mới và lên kế hoạch sửa đổi các ứng dụng hiện đang sử dụng tính năng này. "
sotn

Không giúp gì trong trường hợp của tôi.
st_stefanov

19

Làm điều này cho cơ sở dữ liệu của bạn. Tôi có cùng một vấn đề - nó hoạt động tốt trong một cơ sở dữ liệu nhưng khi tôi sao chép cơ sở dữ liệu này sang cơ sở dữ liệu khác bằng SSIS Nhập (không phải khôi phục thông thường), vấn đề này xảy ra với hầu hết các quy trình được lưu trữ của tôi. Vì vậy, sau khi googling thêm một số, tôi tìm thấy blog của Pinal Dave (btw, tôi đã gặp hầu hết các bài đăng của anh ấy và đã giúp tôi rất nhiều vì vậy cảm ơn Pinal Dave) .

Tôi thực hiện truy vấn dưới đây trên cơ sở dữ liệu của mình và nó đã khắc phục vấn đề của tôi:

EXEC sp_MSforeachtable @command1="print '?' DBCC DBREINDEX ('?', ' ', 80)"
GO
EXEC sp_updatestats
GO 

Hi vọng điêu nay co ich. Chỉ cần thông qua sự giúp đỡ từ những người khác đã giúp tôi.


2
Chỉ là một FYI cho độc giả tương lai: DBCC REINDEXđã bị phản đối, vì vậy bạn nên tìm kiếm các lựa chọn thay thế.
gvee

1
Đã khắc phục sự cố của tôi, cảm ơn (1m20 xuống còn 2 giây!). Re : DBCC DBREINDEX, MS nói: "Tính năng này sẽ bị xóa trong phiên bản tương lai của Microsoft SQL Server. Không sử dụng tính năng này trong công việc phát triển mới và sửa đổi các ứng dụng hiện đang sử dụng tính năng này càng sớm càng tốt. Thay vào đó, hãy sử dụng ALTER INDEX."
AjV Jsy

Không biết đây có phải là câu trả lời hay nhất không nhưng trong trường hợp của tôi, sp_updatestats là tất cả những gì nó cần, vì vậy +1
Todd Menier

.. và đừng quên rằng việc xây dựng lại các chỉ mục có thể mất thời gian và không gian vì vậy trước khi bạn thực hiện việc này trên máy chủ sản xuất của mình, hãy đảm bảo rằng bạn có thể đủ khả năng làm chậm. Tôi sẽ đề nghị xem xét REORGANIZE hoặc REBUILD VỚI (ONLINE = ON)
Milan

14

Tôi đã phải đối mặt với cùng một vấn đề & bài đăng này rất hữu ích cho tôi nhưng không có câu trả lời nào được đăng đã giải quyết vấn đề cụ thể của tôi. Tôi muốn đăng giải pháp hiệu quả với tôi với hy vọng nó có thể giúp đỡ người khác.

https://stackoverflow.com/a/24016676/814299

Khi kết thúc truy vấn của bạn, hãy thêm TÙY CHỌN (TỐI ƯU HÓA CHO (@now UNKNOWN))


4

Lần này bạn đã tìm thấy vấn đề của mình. Nếu lần sau bạn kém may mắn hơn và không thể tìm ra nó, bạn có thể sử dụng đóng băng kế hoạch và ngừng lo lắng về kế hoạch thực hiện sai.


Theo mục blog đó, việc đóng băng kế hoạch chỉ dành cho MS SQL 2005 trở lên, vì vậy nó sẽ không giúp ích gì cho OP.
Coxy

Vấn đề là nó đã sử dụng kế hoạch truy vấn sai. tôi sẽ không muốn đóng băng nó cho sai.
Ian Boyd

4

Tôi đã gặp vấn đề này. Truy vấn của tôi trông giống như:

select a, b, c from sometable where date > '20140101'

Thủ tục lưu trữ của tôi được định nghĩa như sau:

create procedure my_procedure (@dtFrom date)
as
select a, b, c from sometable where date > @dtFrom

Tôi đã thay đổi kiểu dữ liệu thành datetime và voila! Đã đi từ 30 phút đến 1 phút!

create procedure my_procedure (@dtFrom datetime)
as
select a, b, c from sometable where date > @dtFrom

2
Cảm ơn rất nhiều Lee, điều này đã cứu ngày của tôi! Đây là cách tôi chỉ nhận được phần ngày của trường datetime: DATEADD (dd, 0, DATEDIFF (dd, 0, table.field))
Julien B.

1
Điều này đã khắc phục vấn đề của tôi. Tôi đã có cột varchar (20) và tham số của tôi là nvarchar (50), một khi tôi đã tạo kiểu tham số giống như kiểu cột - không có độ trễ nữa.
st_stefanov

3

Bạn đã thử xây dựng lại các số liệu thống kê và / hoặc các chỉ mục trên bảng Report_Opener. Tất cả các bản tóm tắt của SP sẽ không có giá trị gì nếu số liệu thống kê vẫn hiển thị dữ liệu từ khi cơ sở dữ liệu lần đầu tiên được xác định.

Bản thân truy vấn ban đầu hoạt động nhanh chóng vì trình tối ưu hóa có thể thấy rằng tham số sẽ không bao giờ là null. Trong trường hợp SP, trình tối ưu hóa không thể chắc chắn rằng tham số sẽ không bao giờ là null.


Có cách nào để chỉ ra trong một khai báo thủ tục được lưu trữ rằng tham số i không thể rỗng? Và nó không phải là thứ sẽ được sửa bởi sp_executesql?
Ian Boyd

Trong một từ không, không phải vào năm 2000. 2005 đã thêm một gợi ý truy vấn trong đó bạn có thể cung cấp một giá trị mẫu cho một tham số, trình tối ưu hóa sẽ tối ưu hóa như thể nó biết rằng tham số đó luôn được sử dụng. Phải nói rằng tôi thường thấy đây là một vấn đề thống kê.
AnthonyWJones

Nếu đó là một vấn đề về thống kê, thì nó hoạt động tốt từ QA khi tôi chạy nó ad-hoc, sp_executesql, exec (). Và tại sao tất cả chúng sau đó chạy kém khi một thủ tục được lưu trữ chứa ad-hoc sql, sp_executesql, exec ()?
Ian Boyd

1

Mặc dù tôi thường chống lại nó (mặc dù trong trường hợp này có vẻ như bạn có lý do chính đáng), bạn đã thử cung cấp bất kỳ gợi ý truy vấn nào trên phiên bản SP của truy vấn chưa? Nếu SQL Server đang chuẩn bị một kế hoạch thực hiện khác nhau trong hai trường hợp đó, bạn có thể sử dụng một gợi ý để cho nó biết chỉ mục nào sẽ sử dụng, để kế hoạch khớp với kế hoạch đầu tiên không?

Đối với một số ví dụ, bạn có thể vào đây .

EDIT: Nếu bạn có thể đăng kế hoạch truy vấn của mình tại đây, có lẽ chúng tôi có thể xác định một số khác biệt giữa các kế hoạch đang nói.

THỨ HAI: Đã cập nhật liên kết thành SQL-2000 cụ thể. Bạn sẽ phải cuộn xuống một cách, nhưng có một "Gợi ý bảng" thứ hai có tên là thứ bạn đang tìm kiếm.

THIRD: Truy vấn "Xấu" dường như bỏ qua [IX_Openers_SessionGUID] trên bảng "Trình mở" - bất kỳ cơ hội nào thêm một gợi ý INDEX để buộc nó sử dụng chỉ mục đó sẽ thay đổi mọi thứ?


Các gợi ý truy vấn hữu ích nhất trong tài liệu tham khảo đó không có sẵn trên SQL 2000, đây là phiên bản được đề cập ở đây.
AnthonyWJones

Ngoài ra, những gợi ý là cần thiết? SQL Server có thể tìm ra nó không có vấn đề gì khi tôi chạy nó ad-hoc.
Ian Boyd

Chắc chắn, và đó luôn luôn là kinh nghiệm của tôi. Tuy nhiên, trong trường hợp này, anh ta nói rằng nó sẽ đưa ra một kế hoạch thực hiện hoàn toàn khác. Có thể có một chỉ mục được sử dụng đặc biệt, nhưng vì một số lý do đang bị bỏ qua trong Proc. Anh ta có thể buộc SQL Server sử dụng chỉ mục với gợi ý "INDEX".
SqlRyan

1

Điều này có lẽ là không thể, nhưng cho rằng hành vi quan sát của bạn là bất thường, nó cần phải được kiểm tra và không ai khác đã đề cập đến nó.

Bạn có hoàn toàn chắc chắn rằng tất cả các đối tượng được sở hữu bởi dbo và bạn không có một bản sao giả mạo thuộc sở hữu của mình hoặc một người dùng hiện tại khác nhau không?

Chỉ thỉnh thoảng khi tôi thấy hành vi kỳ quặc đó là vì thực sự có hai bản sao của một đối tượng và cái nào bạn nhận được phụ thuộc vào những gì được chỉ định và bạn đăng nhập như thế nào. Ví dụ: hoàn toàn có thể có hai bản sao của chế độ xem hoặc thủ tục có cùng tên nhưng thuộc sở hữu của các chủ sở hữu khác nhau - một tình huống có thể xảy ra khi bạn không đăng nhập vào cơ sở dữ liệu dưới dạng dbo và quên chỉ định dbo là chủ sở hữu đối tượng khi bạn tạo đối tượng.

Lưu ý rằng trong văn bản bạn đang chạy một số thứ mà không chỉ định chủ sở hữu, ví dụ:

sp_recompile ViewOpener

ví dụ: nếu có hai bản sao viewOpener được sở hữu bởi dbo và [một số người dùng khác] thì cái nào bạn thực sự biên dịch lại nếu bạn không chỉ định phụ thuộc vào hoàn cảnh. Ditto với chế độ xem Báo cáo_Opener - nếu có hai bản sao (và chúng có thể khác nhau về đặc điểm kỹ thuật hoặc kế hoạch thực hiện) thì những gì được sử dụng tùy thuộc vào hoàn cảnh - và vì bạn không chỉ định chủ sở hữu nên hoàn toàn có thể truy vấn adhoc của bạn có thể sử dụng một và thủ tục biên dịch có thể sử dụng sử dụng khác.

Như tôi nói, điều đó có thể không xảy ra nhưng có thể và nên được kiểm tra vì vấn đề của bạn có thể là bạn chỉ đang tìm lỗi ở sai vị trí.


1

Điều này nghe có vẻ ngớ ngẩn và có vẻ rõ ràng từ tên SessionGUID, nhưng cột có phải là định danh duy nhất trên Báo cáo_Opener không? Nếu không, bạn có thể muốn thử chuyển nó thành đúng loại và bắn nó hoặc khai báo biến của bạn thành đúng loại.

Kế hoạch được tạo ra như một phần của vòng quay có thể hoạt động không chủ ý và thực hiện một nội bộ trên một bàn lớn.


Không phải vậy. Nhưng tôi đã thấy các vấn đề về hiệu năng với mệnh đề where đang so sánh một varcharcột với một nvarchargiá trị (Ví dụ WHERE CustomerName = N'zrendall'). SQL Server đã phải chuyển đổi mọi giá trị cột thành một nvarchartrước khi so sánh.
Ian Boyd

0

Tôi có một ý tưởng khác. Điều gì nếu bạn tạo chức năng dựa trên bảng này:

CREATE FUNCTION tbfSelectFromView
(   
    -- Add the parameters for the function here
    @SessionGUID UNIQUEIDENTIFIER
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT *
    FROM Report_Opener
    WHERE SessionGUID = @SessionGUID
    ORDER BY CurrencyTypeOrder, Rank
)
GO

Và sau đó được chọn từ nó bằng cách sử dụng câu lệnh sau (thậm chí đặt điều này trong SP của bạn):

SELECT *
FROM tbfSelectFromView(@SessionGUID)

Có vẻ như những gì đang xảy ra (mà mọi người đã nhận xét) là SQL Server chỉ đưa ra một giả định ở đâu đó không đúng và có thể điều này sẽ buộc nó phải sửa chữa giả định. Tôi ghét phải thêm bước bổ sung, nhưng tôi không chắc điều gì khác có thể gây ra.


0

- Đây là giải pháp:

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

-- Đó là nó

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.