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)
và 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)
và with recompile
từ 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)
và with recompile
và:
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.