Đây là một trong những cuộc tranh luận về tôn giáo / chính trị đã diễn ra trong nhiều năm:tôi nên sử dụng các thủ tục được lưu trữ hay tôi nên đưa các truy vấn đặc biệt vào ứng dụng của mình? Tôi luôn là người đề xuất các thủ tục được lưu trữ, vì một vài lý do:
- Tôi không thể triển khai các biện pháp bảo vệ đưa vào SQL nếu truy vấn được tạo trong mã ứng dụng. Các nhà phát triển có thể biết về các truy vấn được tham số hóa nhưng không có gì buộc họ phải sử dụng chúng đúng cách.
- Tôi không thể điều chỉnh một truy vấn được nhúng trong mã nguồn ứng dụng, cũng như không thể thực thi bất kỳ phương pháp hay nhất nào.
- Nếu tôi tìm thấy cơ hội để điều chỉnh truy vấn, để triển khai nó, tôi phải biên dịch lại và triển khai lại mã ứng dụng, thay vì chỉ thay đổi quy trình đã lưu trữ.
- Nếu truy vấn được sử dụng ở nhiều nơi trong ứng dụng hoặc trong nhiều ứng dụng và nó yêu cầu thay đổi, tôi phải thay đổi nó ở nhiều nơi, trong khi với quy trình đã lưu trữ, tôi chỉ phải thay đổi nó một lần (các vấn đề triển khai sang một bên).
Tôi cũng thấy rằng rất nhiều người đang từ bỏ các thủ tục được lưu trữ để ủng hộ ORM. Đối với các ứng dụng đơn giản, điều này có thể sẽ ổn, nhưng khi ứng dụng của bạn trở nên phức tạp hơn, bạn có thể thấy rằng ORM mà bạn lựa chọn chỉ đơn giản là không có khả năng thực hiện các mẫu truy vấn nhất định, * buộc * bạn phải sử dụng một thủ tục được lưu trữ. Nếu nó hỗ trợ các thủ tục được lưu trữ, nghĩa là.
Mặc dù tôi vẫn thấy tất cả những lập luận này khá thuyết phục, nhưng chúng không phải là điều tôi muốn nói đến hôm nay; Tôi muốn nói về hiệu suất.
Rất nhiều lập luận sẽ chỉ đơn giản nói rằng, "các thủ tục được lưu trữ hoạt động tốt hơn!" Điều đó có thể hơi đúng tại một số điểm, nhưng vì SQL Server đã thêm khả năng biên dịch ở cấp câu lệnh chứ không phải cấp đối tượng và đã có được chức năng mạnh mẽ như optimize for ad hoc workloads
, đây không còn là một lập luận quá mạnh mẽ. Việc điều chỉnh chỉ mục và các mẫu truy vấn hợp lý có tác động lớn hơn nhiều đến hiệu suất so với việc chọn sử dụng một thủ tục được lưu trữ trước đây; trên các phiên bản hiện đại, tôi nghi ngờ bạn sẽ tìm thấy nhiều trường hợp mà cùng một truy vấn chính xác thể hiện sự khác biệt đáng chú ý về hiệu suất, trừ khi bạn cũng đang giới thiệu các biến khác (chẳng hạn như chạy một thủ tục cục bộ so với một ứng dụng trong một trung tâm dữ liệu khác trên một lục địa khác).
Điều đó nói rằng, có một khía cạnh hiệu suất thường bị bỏ qua khi xử lý các truy vấn đặc biệt:bộ nhớ cache của kế hoạch. Chúng tôi có thể sử dụng optimize for ad hoc workloads
để ngăn các gói sử dụng một lần lấp đầy bộ nhớ đệm của chúng tôi (Kimberly Tripp (@KimberlyLTripp) của SQLskills.com có một số thông tin tuyệt vời về điều này ở đây) và điều đó ảnh hưởng đến các gói sử dụng một lần bất kể các truy vấn có được chạy từ trong một quy trình được lưu trữ hay không hoặc đang chạy đặc biệt. Một tác động khác mà bạn có thể không nhận thấy, bất kể cài đặt này là gì, là khi giống hệt nhau các kế hoạch chiếm nhiều vị trí trong bộ nhớ cache vì sự khác biệt trong SET
tùy chọn hoặc delta nhỏ trong văn bản truy vấn thực tế. Toàn bộ hiện tượng "chậm trong ứng dụng, nhanh trong SSMS" đã giúp rất nhiều người giải quyết các vấn đề liên quan đến cài đặt như SET ARITHABORT
. Hôm nay, tôi muốn nói về sự khác biệt trong văn bản truy vấn và chứng minh điều gì đó khiến mọi người ngạc nhiên mỗi khi tôi đưa ra.
Bộ nhớ đệm để ghi
Giả sử chúng ta có một hệ thống rất đơn giản đang chạy AdventureWorks2012. Và chỉ để chứng minh rằng nó không hữu ích, chúng tôi đã bật optimize for ad hoc workloads
:
EXEC sp_configure 'hiển thị các tùy chọn nâng cao', 1; GORECONFIGURE WITH OVERRIDE; GOEXEC sp_configure 'tối ưu hóa cho khối lượng công việc đặc biệt', 1; GORECONFIGURE WITH OVERRIDE;
Và sau đó giải phóng bộ nhớ cache của gói:
DBCC FREEPROCCACHE;
Bây giờ chúng tôi tạo một vài biến thể đơn giản cho một truy vấn giống hệt nhau. Các biến thể này có thể đại diện cho các kiểu mã hóa cho hai nhà phát triển khác nhau - sự khác biệt nhỏ về khoảng trắng, chữ hoa / chữ thường, v.v.
SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID> =75120ORDER BY OrderDate DESC; GO - change> =75120 to> 75119 (cùng một logic vì nó là INT) ĐI CHỌN ĐẦU (1) SalesOrderID, Ngày đặt hàng, Ngày bán hàng phụ đối số cho topGO chọn top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc; GO - thêm khoảng trắng sau top 1GO select top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc; GO - loại bỏ khoảng trắng giữa các dấu phẩy trước>Nếu chúng tôi chạy lô đó một lần, và sau đó kiểm tra bộ đệm của kế hoạch, chúng tôi thấy rằng chúng tôi có 6 bản sao của, về cơ bản, cùng một kế hoạch thực thi. Điều này là do văn bản truy vấn được băm nhị phân, có nghĩa là chữ hoa và khoảng trắng tạo ra sự khác biệt và có thể làm cho các truy vấn giống hệt nhau trông duy nhất đối với SQL Server.
SELECT [text], size_in_bytes, usecounts, cacheobjtypeFROM sys.dm_exec_cached_plans AS pCROSS ÁP DỤNG sys.dm_exec_sql_text (p.plan_handle) AS tWHERE LOWER (t. [text]) LIKE '% ales.sales%' + 'orderheader;Kết quả:
text | size_in_bytes | số tiền sử dụng | cacheobjtype |
---|---|---|---|
chọn top 1 salesorderid, o… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
chọn top 1 salesorderid,… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
chọn top 1 salesorderid, o… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
select top (1) salesorderid,… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
CHỌN HÀNG ĐẦU (1) SalesOrderID,… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
CHỌN HÀNG ĐẦU (1) SalesOrderID,… | 272 | 1 | Tổng hợp kế hoạch tổng hợp |
Kết quả sau lần thực hiện đầu tiên của các truy vấn "giống hệt"
Vì vậy, điều này hoàn toàn không lãng phí, vì cài đặt đặc biệt đã cho phép SQL Server chỉ lưu trữ các sơ khai nhỏ trong lần thực thi đầu tiên. Mặc dù vậy, nếu chúng tôi chạy lại loạt (mà không giải phóng bộ đệm thủ tục), chúng tôi thấy kết quả đáng báo động hơn một chút:
text | size_in_bytes | số tiền sử dụng | cacheobjtype |
---|---|---|---|
chọn top 1 salesorderid, o… | 49.152 | 1 | Kế hoạch Tổng hợp |
chọn top 1 salesorderid,… | 49.152 | 1 | Kế hoạch Tổng hợp |
chọn top 1 salesorderid, o… | 49.152 | 1 | Kế hoạch Tổng hợp |
select top (1) salesorderid,… | 49.152 | 1 | Kế hoạch Tổng hợp |
CHỌN HÀNG ĐẦU (1) SalesOrderID,… | 49.152 | 1 | Kế hoạch Tổng hợp |
CHỌN HÀNG ĐẦU (1) SalesOrderID,… | 49.152 | 1 | Kế hoạch Tổng hợp |
Kết quả sau khi thực hiện lần thứ hai các truy vấn "giống hệt"
Điều tương tự cũng xảy ra đối với các truy vấn được tham số hóa, bất kể việc tham số hóa là đơn giản hay bắt buộc. Và điều tương tự cũng xảy ra khi cài đặt đặc biệt không được bật, ngoại trừ việc nó xảy ra sớm hơn.
Kết quả thực tế là điều này có thể tạo ra rất nhiều bộ nhớ cache kế hoạch phình to, ngay cả đối với các truy vấn trông giống hệt nhau - tất cả các truy vấn trong đó một nhà phát triển thụt lề bằng một tab và người kia thụt lề với 4 dấu cách. Tôi không cần phải nói với bạn rằng cố gắng thực thi kiểu nhất quán này trong một nhóm có thể từ tẻ nhạt đến bất khả thi. Vì vậy, trong suy nghĩ của tôi, điều này cho thấy sự ủng hộ mạnh mẽ đối với việc mô-đun hóa, nhượng bộ DRY và tập trung loại truy vấn này vào một thủ tục được lưu trữ duy nhất.
Một cảnh báo
Tất nhiên, nếu bạn đặt truy vấn này trong một thủ tục được lưu trữ, bạn sẽ chỉ có một bản sao của nó, vì vậy bạn hoàn toàn tránh được khả năng có nhiều phiên bản của truy vấn với văn bản truy vấn hơi khác nhau. Bây giờ, bạn cũng có thể tranh luận rằng những người dùng khác nhau có thể tạo cùng một thủ tục được lưu trữ với các tên khác nhau và trong mỗi thủ tục được lưu trữ có một chút biến thể của văn bản truy vấn. Trong khi có thể, tôi nghĩ rằng đó là một vấn đề hoàn toàn khác. :-)