Là kế hoạch thực hiện được lưu trong bộ nhớ cache tốt hơn cho các thủ tục được lưu trữ hơn là cho một truy vấn không động?


7

Đọc các giải thích khác nhau về bộ nhớ đệm kế hoạch thực hiện của Microsoft SQL Server, tôi bối rối về lợi ích của việc sử dụng các thủ tục được lưu trữ thay vì các truy vấn không động.

Theo một truy vấn không động, tôi có nghĩa là một chuỗi truy vấn được tham số hóa đầy đủ mà không thay đổi qua nhiều lần gọi.

Như tôi hiểu nó:

  1. Kế hoạch thực hiện được lưu trữ cả cho một thủ tục được lưu trữ và một truy vấn thông thường.

  2. Đối với một thủ tục được lưu trữ, kế hoạch thực hiện được tính toán trước, điều này dẫn đến một lợi ích nhỏ so với các truy vấn thông thường trong lần đầu tiên thủ tục được lưu trữ được gọi.

Các nguồn trông khá mâu thuẫn với tôi:

  • Bài viết Bộ nhớ đệm và Tái sử dụng Kế hoạch thực hiện trên MSDN không tạo ra sự khác biệt giữa các truy vấn được tham số hóa và các thủ tục được lưu trữ. Các phần phụ nhấn mạnh tầm quan trọng của các truy vấn được tham số hóa để giúp SQL Server dễ dàng lưu trữ kế hoạch thực hiện.

  • Các kế hoạch thực hiện truy vấn SQL Server - Khái niệm cơ bản ngược lại (nhấn mạnh của tôi):

    Khi thực hiện các truy vấn ad hoc, các gói truy vấn được tạo dựa trên mã hoàn chỉnh, do đó các tham số khác nhau hoặc bất kỳ thay đổi nào trong mã sẽ ngăn việc sử dụng lại kế hoạch hiện có .

  • Trên DBA.StackExchange, nhận xét về câu trả lời liên quan đến lợi ích của các thủ tục được lưu trữ chỉ ra rằng các truy vấn được tham số hóa có chính xác hiệu quả tương tự như các thủ tục được lưu trữ.

Vì vậy, trong bối cảnh kế hoạch thực hiện không bị xóa khỏi bộ đệm và khi, để thử nghiệm, tôi muốn chạy hàng tỷ lần một truy vấn khá phức tạp sẽ có lợi từ kế hoạch thực hiện và có một tham số thay đổi mỗi lần, sẽ có bất kỳ lợi ích nào về bộ nhớ đệm kế hoạch thực hiện để sử dụng các thủ tục được lưu trữ thay vì truy vấn tham số thông thường?


Ngoài phạm vi của kế hoạch thực hiện, sẽ có những lợi ích hiệu suất nhỏ khi sử dụng một thủ tục được lưu trữ, ví dụ về mặt dấu chân mạng: chuyển tên của thủ tục được lưu trữ và các tham số của nó tốt hơn một chút so với chuyển toàn bộ truy vấn. Những lợi ích đó nằm ngoài phạm vi câu hỏi của tôi, hoàn toàn là về bộ đệm của kế hoạch thực hiện.


2
Chưa bao giờ thấy nó được thử nghiệm nên không thể nhận xét về thời gian thực hiện bên lề hoặc không. Điều đó nói rằng, tôi nghĩ rằng sự cảnh báo lý thuyết của bạn cần phải được xem xét lại .... những byte đó nhỏ đến mức nào? Chênh lệch kích thước gói 100 byte khác nhau không chỉ là về độ trễ mạng tổng hợp và các gói ... những byte tương tự cần được trình phân tích cú pháp băm để xác định xem đã có gói chưa. Tương tự như vậy, rất hiếm khi tôi thấy một Proc được lưu trữ chỉ với một truy vấn ... logic nặng nề và logic kinh doanh mà bạn có thể nhúng là các yếu tố ghi đè cũng như lý do tại sao nếu nó đủ quan trọng của một truy vấn, bạn nên
Andrew Loree

Câu trả lời:


4

Câu trả lời cũng có sẵn như là một bài viết blog độc lập .

Để tìm ra nó, tôi đã làm một số thử nghiệm. Mục tiêu là để có cùng một truy vấn tham số được thực hiện trực tiếp từ C # hoặc bằng cách gọi một thủ tục được lưu trữ và để so sánh hiệu năng thời gian chạy.

Tôi bắt đầu tạo một thủ tục được lưu trữ để thực hiện một truy vấn mẫu bằng cơ sở dữ liệu Adventure Works:

create procedure Demo
    @minPrice int 
as
begin
    set nocount on;

    select top 1 [p].[Name], [p].[ProductNumber], [ph].[ListPrice]
    from [Production].[Product] p
    inner join [Production].[ProductListPriceHistory] ph
    on [p].[ProductID] = ph.[ProductID]
    and ph.[StartDate] =
    (
        select top 1 [ph2].[StartDate]
        from [Production].[ProductListPriceHistory] ph2
        where [ph2].[ProductID] = [p].[ProductID]
        order by [ph2].[StartDate] desc
    )
    where [p].[ListPrice] > @minPrice
end

Sau đó, tôi sử dụng đoạn mã sau để so sánh các màn trình diễn:

long RunQuery(SqlConnection connection, int minPrice)
{
    const string Query = @"
    select top 1 [p].[Name], [p].[ProductNumber], [ph].[ListPrice]
    from [Production].[Product] p
    inner join [Production].[ProductListPriceHistory] ph
    on [p].[ProductID] = ph.[ProductID]
    and ph.[StartDate] =
    (
        select top 1 [ph2].[StartDate]
        from [Production].[ProductListPriceHistory] ph2
        where [ph2].[ProductID] = [p].[ProductID]
        order by [ph2].[StartDate] desc
    )
    where [p].[ListPrice] > @minPrice
    option (recompile)";

    using (var command = new SqlCommand(Query, connection))
    {
        command.Parameters.AddWithValue("@minPrice", minPrice);
        var stopwatch = Stopwatch.StartNew();
        command.ExecuteNonQuery();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

long RunStoredProcedure(SqlConnection connection, int minPrice)
{
    using (var command = new SqlCommand("exec Demo @minPrice with recompile", connection))
    {
        command.Parameters.AddWithValue("@minPrice", minPrice);
        var stopwatch = Stopwatch.StartNew();
        command.ExecuteNonQuery();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

ICollection<long> Execute(Func<SqlConnection, int, long> action)
{
    using (var connection = new SqlConnection("Server=.;Database=AdventureWorks2014;Trusted_Connection=True;"))
    {
        connection.Open();
        using (var command = new SqlCommand("DBCC FreeProcCache; DBCC DropCleanbuffers;", connection))
        {
            command.ExecuteNonQuery();
        }

        return Enumerable.Range(0, 100).Select(i => action(connection, i)).ToList();
    }
}

void Main()
{
    var queries = Execute(RunQuery);
    var storedProcedures = Execute(RunStoredProcedure);

    Console.WriteLine("Stored procedures: {0} ms. Details: {1}.", storedProcedures.Sum(), string.Join(", ", storedProcedures));
    Console.WriteLine("Queries: {0} ms. Details: {1}.", queries.Sum(), string.Join(", ", queries));
}

Thông báo option (recompile)with recompile. Điều này sẽ buộc SQL Server loại bỏ các kế hoạch thực hiện được lưu trong bộ nhớ cache trước đó.

Mỗi truy vấn được chạy une hàng trăm lần với một tham số khác nhau mỗi lần. Thời gian dành cho máy chủ được đo ở phía máy khách.

Bằng cách chạy DBCC FreeProcCache; DBCC DropCleanbuffers;trước khi thu thập số liệu, tôi đảm bảo rằng tất cả các gói thực thi được lưu trong bộ nhớ cache trước đó đã được xóa.

Chạy mã này cho đầu ra sau:

Thủ tục lưu trữ: 786 ms. Chi tiết: 12, 7, 7, 9, 7, 7, 9, 8, 8, 6, 8, 9, 8, 8, 14, 8, 7, 8, 7, 10, 10, 7, 9, 6, 9, 8, 8, 7, 7, 10, 8, 7, 7, 6, 7, 8, 8, 7, 7, 7, 14, 8, 8, 8, 7, 9, 8, 8, 7, 6, 6, 12, 7, 7, 8, 7, 8, 7, 8, 6, 7, 7, 7, 12, 8, 6, 6, 7, 8, 7, 8, 8, 7, 11, 8, 7, 8, 8, 7, 9, 8, 9, 10, 8, 7, 7, 8, 8, 7, 9, 7, 6, 9, 7, 6, 9, 8, 6, 6, 6.
Truy vấn: 799 ms. Chi tiết: 21, 8, 8, 7, 6, 6, 11, 7, 6, 6, 9, 8, 8, 7, 9, 8, 7, 7, 7, 7, 7, 7, 10, 8, 8, 7, 8, 7, 6, 11, 19, 10, 8, 7, 8, 7, 7, 7, 6, 9, 7, 9, 7, 7, 8, 7, 12, 9, 7, 7, 7, 8, 7, 7, 8, 7, 7, 7, 9, 8, 7, 7, 7, 6, 7, 7, 16, 7, 7, 7, 8, 8, 9, 8, 7, 9, 8, 7, 8, 7, 7, 6, 7, 7, 7, 7, 12, 7, 9, 9, 7, 7, 7, 7, 9, 8, 7, 8, 11, số 8.

Hãy chạy lại:

Thủ tục lưu trữ: 763 ms. Chi tiết: 11, 8, 10, 8, 8, 14, 10, 6, 7, 7, 6, 7, 7, 9, 6, 6, 6, 8, 6, 6, 7, 6, 8, 7, 16, 8, 7, 8, 9, 7, 7, 8, 7, 7, 11, 10, 7, 6, 7, 8, 7, 7, 7, 7, 7, 7, 10, 9, 9, 7, 6, 7, 6, 7, 7, 6, 6, 6, 6, 6, 10, 9, 10, 7, 6, 6, 6, 6, 6, 8, 7, 6, 6, 7, 8, 9, 7, 8, 7, 10, 7, 7, 7, 6, 7, 6, 7, 11, 13, 8, 7, 10, 9, 8, 8, 7, 8, 7, 7, 7.
Truy vấn: 752 ms. Chi tiết: 25, 10, 8, 8, 12, 8, 7, 9, 9, 8, 6, 7, 7, 6, 8, 6, 7, 7, 8, 9, 7, 7, 7, 7, 6, 10, 8, 7, 7, 7, 7, 7, 7, 7, 8, 9, 7, 6, 6, 6, 7, 13, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 10, 7, 7, 8, 9, 8, 7, 6, 6, 7, 7, 9, 7, 8, 6, 9, 7, 7, 8, 7, 6, 6, 7, 7, 7, 7, 6, 7, 7, 8, 7, 7, 6, 7, 9, 8, 7, 7, 7, 7, 6, 7, 6, 6, 9, 7, 7.

Có vẻ như hiệu suất rất gần giữa các thủ tục được lưu trữ và các truy vấn trực tiếp. Chạy mã hàng chục lần, tôi nhận thấy rằng các thủ tục được lưu trữ có vẻ hơi nhanh, nhưng khoảng cách rất hẹp. Có thể chuyển xung quanh toàn bộ truy vấn sẽ tạo ra chi phí bổ sung này, có thể tăng nếu SQL Server được lưu trữ trên một máy chuyên dụng có mạng LAN chậm giữa nó và máy chủ ứng dụng.

Bây giờ chúng ta hãy bật bộ nhớ đệm kế hoạch thực hiện và xem điều gì sẽ xảy ra. Để làm điều này, tôi loại bỏ option (recompile)with recompiletừ mã. Đây là đầu ra mới:

Thủ tục lưu trữ: 26 ms. Chi tiết: 23, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.
Truy vấn: 15 ms. Chi tiết: 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.

Rõ ràng là bộ nhớ đệm có tác dụng chính xác cho cả truy vấn trực tiếp và các thủ tục được lưu trữ. Trong cả hai trường hợp, nó giảm thời gian xuống gần 0 mili giây và truy vấn đắt nhất là truy vấn đầu tiên chạy sau khi loại bỏ các kế hoạch thực hiện được lưu trong bộ nhớ cache.

Chạy cùng mã một lần nữa cho thấy một mô hình tương tự. Đôi khi, các truy vấn nhanh hơn, và đôi khi các thủ tục được lưu trữ. Nhưng mỗi lần, truy vấn đầu tiên là truy vấn đắt nhất và tất cả các truy vấn khác gần bằng 0 mili giây.

Mở lại kết nối SQL

Nếu kết nối SQL được mở cho mọi truy vấn, chẳng hạn như trong mã được sửa đổi một chút này:

long RunQuery(string connectionString, int minPrice)
{
    const string Query = @"
    select top 1 [p].[Name], [p].[ProductNumber], [ph].[ListPrice]
    from [Production].[Product] p
    inner join [Production].[ProductListPriceHistory] ph
    on [p].[ProductID] = ph.[ProductID]
    and ph.[StartDate] =
    (
        select top 1 [ph2].[StartDate]
        from [Production].[ProductListPriceHistory] ph2
        where [ph2].[ProductID] = [p].[ProductID]
        order by [ph2].[StartDate] desc
    )
    where [p].[ListPrice] > @minPrice
    option (recompile)";

    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var command = new SqlCommand(Query, connection))
        {
            command.Parameters.AddWithValue("@minPrice", minPrice);
            var stopwatch = Stopwatch.StartNew();
            command.ExecuteNonQuery();
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }
    }
}

long RunStoredProcedure(string connectionString, int minPrice)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var command = new SqlCommand("exec Demo @minPrice with recompile", connection))
        {
            command.Parameters.AddWithValue("@minPrice", minPrice);
            var stopwatch = Stopwatch.StartNew();
            command.ExecuteNonQuery();
            stopwatch.Stop();
            return stopwatch.ElapsedMilliseconds;
        }
    }
}

ICollection<long> Execute(Func<string, int, long> action)
{
    var connectionString = "Server=.;Database=AdventureWorks2014;Trusted_Connection=True;";
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        using (var command = new SqlCommand("DBCC FreeProcCache; DBCC DropCleanbuffers;", connection))
        {
            command.ExecuteNonQuery();
        }
    }

    return Enumerable.Range(0, 100).Select(i => action(connectionString, i)).ToList();
}

void Main()
{
    var queries = Execute(RunQuery);
    var storedProcedures = Execute(RunStoredProcedure);

    Console.WriteLine("Stored procedures: {0} ms. Details: {1}.", storedProcedures.Sum(), string.Join(", ", storedProcedures));
    Console.WriteLine("Queries: {0} ms. Details: {1}.", queries.Sum(), string.Join(", ", queries));
}

các số liệu quan sát rất giống nhau:

Thủ tục lưu trữ: 748 ms. Chi tiết: 11, 8, 6, 6, 8, 9, 9, 8, 8, 7, 6, 8, 7, 9, 6, 6, 6, 6, 6, 6, 7, 7, 6, 9, 6, 6, 7, 6, 6, 7, 8, 6, 7, 7, 7, 13, 7, 7, 8, 7, 8, 8, 7, 7, 7, 7, 6, 7, 8, 8, 8, 9, 7, 6, 8, 7, 6, 7, 6, 6, 6, 6, 8, 12, 7, 9, 9, 6, 7, 7, 7, 8, 10, 12, 8, 7, 6, 9, 8, 7, 6, 6, 7, 8, 6, 6, 12, 7, 8, 10, 10, 7, 8, 7, 8, 10, 8, 7, 8, 7.
Truy vấn: 761 ms. Chi tiết: 31, 9, 7, 6, 6, 8, 7, 7, 7, 7, 7, 6, 8, 7, 6, 6, 7, 10, 8, 10, 9, 7, 7, 7, 7, 10, 13, 7, 10, 7, 6, 6, 6, 8, 7, 7, 7, 7, 7, 7, 7, 9, 7, 7, 7, 6, 6, 6, 9, 7, 7, 7, 7, 7, 6, 8, 10, 7, 7, 7, 7, 7, 7, 7, 8, 6, 10, 10, 7, 8, 8, 7, 7, 7, 7, 7, 6, 6, 7, 6, 8, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 7, 9, 7, 6, 6, 12, 10, 7, 6.

với option (recompile)with recompilevà:

Thủ tục lưu trữ: 15 ms. Chi tiết: 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.
Truy vấn: 32 ms. Chi tiết: 26, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0.

không có.

Dưới mui xe

Hãy xem những gì xảy ra dưới mui xe. Truy vấn sau đây cho thấy các kế hoạch thực hiện được lưu trữ:

select usecounts, size_in_bytes, cacheobjtype, objtype, text 
from sys.dm_exec_cached_plans 
cross apply sys.dm_exec_sql_text(plan_handle)
where cacheobjtype = 'Compiled Plan'
order by usecounts desc

Khi chạy truy vấn này sau khi thực hiện các thủ tục được lưu trữ một trăm lần, kết quả của truy vấn sẽ như sau:

usecounts   size_in_bytes cacheobjtype                                       objtype              text
----------- ------------- -------------------------------------------------- -------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
100         90112         Compiled Plan                                      Proc                 create procedure Demo
    @minPrice int 
as
begin
    set nocount on;

    select top 1 [p].[Name], [p].[ProductNumber], [ph].[ListPrice]
    from [Production].[Product] p
    inner join [Production].[ProductListPriceHistory] ph
    on [p].[ProductID] = ph.[Product
100         16384         Compiled Plan                                      Prepared             (@minPrice int)exec Demo @minPrice --with recompile
1           49152         Compiled Plan                                      Adhoc                --DBCC FreeProcCache
--DBCC DropCleanbuffers

select usecounts, size_in_bytes, cacheobjtype, objtype, text 
from sys.dm_exec_cached_plans 
cross apply sys.dm_exec_sql_text(plan_handle)
where cacheobjtype = 'Compiled Plan'
order by usecounts desc

(3 row(s) affected)

Khi chạy truy vấn trực tiếp một trăm lần, kết quả là:

usecounts   size_in_bytes cacheobjtype                                       objtype              text
----------- ------------- -------------------------------------------------- -------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
100         73728         Compiled Plan                                      Prepared             (@minPrice int)
    select top 1 [p].[Name], [p].[ProductNumber], [ph].[ListPrice]
    from [Production].[Product] p
    inner join [Production].[ProductListPriceHistory] ph
    on [p].[ProductID] = ph.[ProductID]
    and ph.[StartDate] =
    (
        select top 1 [ph2].[
1           49152         Compiled Plan                                      Adhoc                --DBCC FreeProcCache
--DBCC DropCleanbuffers

select usecounts, size_in_bytes, cacheobjtype, objtype, text 
from sys.dm_exec_cached_plans 
cross apply sys.dm_exec_sql_text(plan_handle)
where cacheobjtype = 'Compiled Plan'
order by usecounts desc

(2 row(s) affected)

Phần kết luận

  • Kế hoạch thực hiện được lưu trữ cho các thủ tục được lưu trữ và truy vấn trực tiếp.

  • Hiệu năng giữa các thủ tục được lưu trữ và truy vấn trực tiếp rất giống nhau khi SQL Server và ứng dụng được lưu trữ trên cùng một máy. Khi SQL Server được lưu trữ trên một máy chủ chuyên dụng được truy cập qua mạng LAN, sử dụng các quy trình được lưu trữ có thể mang lại hiệu suất tốt hơn.


0

Theo như bộ nhớ đệm kế hoạch, một thủ tục được lưu trữ là một truy vấn được tham số hóa, chúng sử dụng cùng một kế hoạch truy vấn. Có thể có một chút chi phí trong việc sử dụng cái này so với cái kia chỉ do sự khác biệt trong cách họ đến giai đoạn thực hiện, nhưng tôi chưa bao giờ nhận thấy sự khác biệt đáng kể.

Những lợi ích của các thủ tục được lưu trữ có liên quan đến bảo mật, khả năng bảo trì và triển khai. Tất nhiên, bên cạnh sự bảo vệ vốn có của các truy vấn được tham số hóa so với SQL động trường học cũ.

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.