Ý nghĩa và lợi ích của việc sử dụng SqlCommand.Prepare () là gì?


14

Tôi đã xem qua mã nhà phát triển nơi phương thức SqlCommand.Prepare () (xem MSDN) được sử dụng rộng rãi trước khi thực hiện các truy vấn SQL. Và tôi tự hỏi lợi ích của việc này là gì?

Mẫu vật:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

Tôi đã chơi xung quanh một chút và truy tìm. Việc thực thi Lệnh sau khi gọi Prepare()phương thức làm cho Sql Server thực thi câu lệnh sau:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Sau đó, khi Thông số nhận được giá trị của nó và SqlCommand.ExecuteNonQuery()được gọi, phần sau sẽ được thực thi trên Sql-Server:

exec sp_execute 1,@id=20

Đối với tôi điều này có vẻ như câu lệnh được biên dịch ngay khi Prepare()được thực thi. Tôi tự hỏi lợi ích của việc này là gì? Điều này có nghĩa là nó được đưa vào bộ đệm của kế hoạch và có thể được sử dụng lại ngay khi truy vấn cuối cùng được thực hiện với các giá trị tham số mong muốn?

Tôi đã tìm ra (và ghi lại nó trong một câu hỏi khác ) rằng SqlCommands được thực thi với SqlParameter luôn được gói trong sp_executesqlcác lệnh gọi thủ tục. Điều này làm cho Sql Server có thể lưu trữ và sử dụng lại các gói độc lập với các giá trị tham số.

Về điều này, tôi tự hỏi nếu prepare()phương pháp này là vô dụng hoặc lỗi thời hoặc nếu tôi đang thiếu một cái gì đó ở đây?


Tôi luôn nghĩ rằng nó có liên quan đến việc ngăn chặn SQL Injection, vì vậy bạn sẽ chuẩn bị truy vấn để chấp nhận một số biến nhất định của các loại nhất định. Theo cách đó, nếu bạn không chuyển các biến của các loại đó, truy vấn không thành công
Mark Sinkinson 18/2/2016

Mặc dù khi nói rằng, lời giải thích PHP này có lẽ chỉ là hợp lệ cho .NET php.net/manual/en/pdo.prepared-statements.php
Đánh dấu Sinkinson

"Vâng, thưa ngài. Tài xế, chuẩn bị di chuyển ra ngoài." "Bạn đang chuẩn bị gì?! Bạn luôn chuẩn bị! Chỉ cần đi!"
Jon của tất cả các giao dịch

Câu trả lời:


19

Chuẩn bị một lô SQL riêng biệt với việc thực hiện lô SQL đã chuẩn bị là một cấu trúc có hiệu quả **vô dụng đối với SQL Server được đưa ra cách các kế hoạch thực hiện được lưu trữ. Tách các bước chuẩn bị (phân tích cú pháp, ràng buộc bất kỳ tham số nào và biên dịch) và thực thi chỉ có ý nghĩa khi không có bộ đệm. Mục đích là để tiết kiệm thời gian dành cho việc phân tích cú pháp và biên dịch bằng cách sử dụng lại một gói hiện có và đây là những gì bộ đệm của kế hoạch nội bộ thực hiện. Nhưng không phải tất cả các RDBMS đều thực hiện mức bộ nhớ đệm này (rõ ràng từ sự tồn tại của các chức năng này) và do đó, nó tùy thuộc vào mã máy khách để yêu cầu một gói được "lưu vào bộ nhớ cache" và do đó lưu ID bộ đệm đó và sau đó sử dụng lại nó Đây là gánh nặng bổ sung cho mã máy khách (và lập trình viên phải nhớ thực hiện và làm cho đúng) không cần thiết. Trong thực tế, trang MSDN cho phương thức IDbCommand.Prepare () nêu:

Máy chủ tự động lưu trữ các kế hoạch để sử dụng lại khi cần thiết; do đó, không cần phải gọi phương thức này trực tiếp trong ứng dụng khách của bạn.

Cần lưu ý rằng, theo như thử nghiệm của tôi cho thấy (phù hợp với những gì bạn hiển thị trong Câu hỏi), gọi SqlCommand.Prepare () , không thực hiện thao tác "chuẩn bị": nó gọi sp_prepexec để chuẩn bị thực thi SQL ; nó không gọi sp_prepare là phân tích cú pháp và chỉ biên dịch (và không nhận bất kỳ giá trị tham số nào, chỉ có tên và kiểu dữ liệu của chúng). Do đó, không thể có bất kỳ lợi ích "biên dịch trước" nào khi gọi SqlCommand.Preparevì nó thực hiện ngay lập tức (giả sử mục tiêu là trì hoãn thực thi). TUY NHIÊN , có một lợi ích nhỏ tiềm năng thú vị của việc gọi điện SqlCommand.Prepare(), ngay cả khi nó gọi sp_prepexecthay vì sp_prepare. Xin vui lòng xem ghi chú ( ** ) ở phía dưới để biết chi tiết.

Thử nghiệm của tôi cho thấy ngay cả khi SqlCommand.Prepare()được gọi (và xin lưu ý rằng cuộc gọi này không đưa ra bất kỳ lệnh nào trên SQL Server), ExecuteNonQueryhoặc lệnh ExecuteReadertiếp theo được thực thi đầy đủ và nếu là ExecuteReader, nó sẽ trả về các hàng. Tuy nhiên, SQL Server Profiler cho thấy SqlCommand.Execute______()cuộc gọi đầu tiên sau khi SqlCommand.Prepare()đăng ký cuộc gọi là sự kiện "Chuẩn bị SQL" và các .Execute___()cuộc gọi tiếp theo đăng ký dưới dạng sự kiện "Exec Prepared SQL".


Mã kiểm tra

Nó đã được tải lên PasteBin tại: http://pastebin.com/Yc2Tfvup . Mã này tạo ra Ứng dụng Bảng điều khiển .NET / C # dự định chạy trong khi theo dõi SQL Server Profiler đang chạy (hoặc phiên Sự kiện mở rộng). Nó tạm dừng sau mỗi bước để rõ ràng câu lệnh nào có ảnh hưởng cụ thể.


CẬP NHẬT

Tìm thấy thêm thông tin, và một lý do tiềm năng nhỏ để tránh gọi SqlCommand.Prepare(). Một điều tôi nhận thấy trong thử nghiệm của mình và điều cần lưu ý đối với bất kỳ ai đang chạy thử nghiệm Ứng dụng Console đó: không bao giờ có một cuộc gọi rõ ràng được thực hiện sp_unprepare. Tôi đã thực hiện một số hoạt động đào và tìm thấy bài đăng sau đây trong các diễn đàn MSDN:

SqlCommand - Chuẩn bị hay không chuẩn bị?

Câu trả lời được chấp nhận chứa một loạt thông tin, nhưng các điểm nổi bật là:

  • ** Những gì chuẩn bị thực sự giúp bạn tiết kiệm chủ yếu là thời gian cần thiết để truyền chuỗi truy vấn qua dây.
  • Một truy vấn đã chuẩn bị không đảm bảo kế hoạch được lưu trong bộ nhớ cache và không nhanh hơn truy vấn đặc biệt khi nó đến máy chủ.
  • RPC (CommandType.StoredProcedure) không thu được gì từ việc chuẩn bị.
  • Trên máy chủ, một tay cầm được chuẩn bị về cơ bản là một chỉ mục vào bản đồ trong bộ nhớ có chứa TSQL để thực thi.
  • Bản đồ được lưu trữ trên cơ sở mỗi kết nối, không thể sử dụng kết nối chéo.
  • Thiết lập lại được gửi khi khách hàng sử dụng lại kết nối từ nhóm xóa bản đồ.

Tôi cũng tìm thấy bài đăng sau đây từ nhóm SQLCAT, có vẻ như có liên quan, nhưng đơn giản có thể là một vấn đề cụ thể đối với ODBC trong khi SqlClient không dọn dẹp chính xác khi xử lý SqlConnection. Thật khó để nói vì bài viết SQLCAT không đề cập đến bất kỳ thử nghiệm bổ sung nào có thể giúp chứng minh điều này là nguyên nhân, chẳng hạn như xóa nhóm kết nối, v.v.

Xem ra các câu lệnh SQL đã chuẩn bị


PHẦN KẾT LUẬN

Cho rằng:

  • lợi ích thực sự duy nhất của việc gọi điện SqlCommand.Prepare()dường như là bạn không cần gửi lại văn bản truy vấn qua mạng,
  • gọi sp_preparesp_prepexeclưu trữ văn bản truy vấn như một phần của bộ nhớ của kết nối (tức là bộ nhớ Máy chủ SQL)

Tôi muốn giới thiệu với gọi SqlCommand.Prepare()vì lợi ích duy nhất tiềm năng tiết kiệm gói mạng trong khi nhược điểm là chiếm bộ nhớ máy chủ hơn. Mặc dù phần lớn dung lượng bộ nhớ tiêu thụ có thể rất nhỏ trong phần lớn các trường hợp, băng thông mạng hiếm khi là vấn đề vì hầu hết các máy chủ DB được kết nối trực tiếp với các máy chủ ứng dụng trên 10 Megabit tối thiểu (nhiều khả năng là 100 Megabit hoặc Gigabit trong những ngày này) ( và một số thậm chí trên cùng một hộp ;-). Điều đó và bộ nhớ gần như luôn là một nguồn tài nguyên khan hiếm hơn băng thông mạng.

Tôi cho rằng, đối với bất kỳ ai viết phần mềm có thể kết nối với nhiều cơ sở dữ liệu, thì sự tiện lợi của giao diện chuẩn có thể thay đổi phân tích chi phí / lợi ích này. Nhưng ngay cả trong trường hợp đó, tôi phải tin rằng vẫn đủ dễ dàng để trừu tượng một giao diện DB "tiêu chuẩn" cho phép các nhà cung cấp khác nhau (và do đó có sự khác biệt giữa họ, như không cần gọi Prepare()) vì cho rằng làm như vậy là Đối tượng cơ bản Lập trình định hướng mà bạn có khả năng đã thực hiện trong mã ứng dụng của mình ;-).


Cảm ơn bạn cho câu trả lời rộng rãi này. Tôi đã kiểm tra các bước của bạn và có hai quang sai từ mong đợi / kết quả của bạn: ở bước 4 tôi nhận được một kết quả bổ sung. Ở bước 10, tôi có một plan_handle khác so với bước 2.
Magier

1
@Magier Không chắc chắn về bước 4 ... có thể bạn có các tùy chọn SET khác nhau hoặc bằng cách nào đó văn bản truy vấn giữa hai loại này khác nhau? Ngay cả một nhân vật duy nhất (có thể nhìn thấy hoặc không) sẽ là một kế hoạch khác nhau. Sao chép và dán từ trang web này có thể không giúp ích, vì vậy hãy sao chép truy vấn (mọi thứ giữa các dấu ngoặc đơn) từ sp_preparecuộc gọi đến sp_executesql, để đảm bảo. Đối với bước 10, yeah, tôi đã thử nghiệm nhiều hơn vào ngày hôm qua và đã thấy một số dịp với các tay cầm khác nhau sp_prepare, nhưng vẫn không bao giờ giống nhau sp_executesql. Tôi sẽ kiểm tra một điều nữa và cập nhật câu trả lời của tôi.
Solomon Rutzky

1
@Magier Trên thực tế, bài kiểm tra mà tôi đã thực hiện trước đó đã bao quát nó và tôi không nghĩ rằng có bất kỳ việc sử dụng lại kế hoạch thực sự nào, đặc biệt là trong bài đăng trên diễn đàn MSDN nói rằng nó chỉ lưu trữ SQL. Tôi vẫn tò mò làm thế nào nó có thể sử dụng lại plan_handle trong khi sp_executesqlkhông thể hoặc không. Nhưng bất kể, thời gian phân tích vẫn còn sp_preparenếu kế hoạch đã bị xóa khỏi bộ đệm nên tôi sẽ xóa phần đó trong câu trả lời của tôi (thú vị nhưng không liên quan). Dù sao thì phần đó cũng là một ghi chú thú vị vì nó không giải quyết được câu hỏi trực tiếp của bạn. Mọi thứ khác vẫn được áp dụng.
Solomon Rutzky

1
Đây là loại giải thích tôi đang tìm kiếm.
CSharpie 11/03/2016

5

Về điều này, tôi tự hỏi nếu phương thức chuẩn bị () là loại vô dụng hoặc lỗi thời hoặc nếu tôi thiếu một cái gì đó ở đây?

Tôi muốn nói rằng phương thức Chuẩn bị có giá trị giới hạn trong thế giới SqlCommand, không phải nó hoàn toàn vô dụng. Một lợi ích là Chuẩn bị là một phần của giao diện IDbCommand mà SqlCommand thực hiện. Điều này cho phép cùng một mã chạy với các nhà cung cấp DBMS khác (có thể yêu cầu Chuẩn bị) mà không cần logic có điều kiện để xác định xem Chuẩn bị có nên được gọi hay không.

Chuẩn bị không có giá trị với Nhà cung cấp .NET cho SQL Server bên cạnh các trường hợp sử dụng này, AFAIK.

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.