Tuần trước, tôi đã viết về những hạn chế của Luôn mã hóa cũng như tác động đến hiệu suất. Tôi muốn đăng thông tin theo dõi sau khi thực hiện nhiều thử nghiệm hơn, chủ yếu là do những thay đổi sau:
- Tôi đã thêm một thử nghiệm cho cục bộ, để xem liệu chi phí mạng có đáng kể hay không (trước đây, thử nghiệm chỉ là từ xa). Tuy nhiên, tôi nên đặt "chi phí mạng" trong dấu ngoặc kép vì đây là hai máy ảo trên cùng một máy chủ vật lý, vì vậy không thực sự là một phân tích kim loại trần thực sự.
- Tôi đã thêm một vài cột bổ sung (không được mã hóa) vào bảng để làm cho nó thực tế hơn (nhưng không thực sự thực tế như vậy).
DateCreated DATETIME NOT NULL DEFAULT SYSUTCDATETIME(), DateModified DATETIME NOT NULL DEFAULT SYSUTCDATETIME(), IsActive BIT NOT NULL DEFAULT 1
Sau đó, thay đổi quy trình truy xuất cho phù hợp:
ALTER PROCEDURE dbo.RetrievePeople AS BEGIN SET NOCOUNT ON; SELECT TOP (100) LastName, Salary, DateCreated, DateModified, Active FROM dbo.Employees ORDER BY NEWID(); END GO
- Đã thêm một thủ tục để cắt ngắn bảng (trước đây tôi đã làm điều đó theo cách thủ công giữa các lần kiểm tra):
CREATE PROCEDURE dbo.Cleanup AS BEGIN SET NOCOUNT ON; TRUNCATE TABLE dbo.Employees; END GO
- Đã thêm một thủ tục để ghi lại thời gian (trước đây tôi đã phân tích cú pháp đầu ra của bảng điều khiển theo cách thủ công):
USE Utility; GO CREATE TABLE dbo.Timings ( Test NVARCHAR(32), InsertTime INT, SelectTime INT, TestCompleted DATETIME NOT NULL DEFAULT SYSUTCDATETIME(), HostName SYSNAME NOT NULL DEFAULT HOST_NAME() ); GO CREATE PROCEDURE dbo.AddTiming @Test VARCHAR(32), @InsertTime INT, @SelectTime INT AS BEGIN SET NOCOUNT ON; INSERT dbo.Timings(Test,InsertTime,SelectTime) SELECT @Test,@InsertTime,@SelectTime; END GO
- Tôi đã thêm một cặp cơ sở dữ liệu sử dụng tính năng nén trang - tất cả chúng ta đều biết rằng các giá trị được mã hóa không nén tốt, nhưng đây là một tính năng phân cực có thể được sử dụng đơn phương ngay cả trên các bảng có cột được mã hóa, vì vậy tôi nghĩ mình sẽ hồ sơ này quá. (Và thêm hai chuỗi kết nối nữa vào
App.Config
.)<connectionStrings> <add name="Normal" connectionString="...;Initial Catalog=Normal;"/> <add name="Encrypt" connectionString="...;Initial Catalog=Encrypt;Column Encryption Setting=Enabled;"/> <add name="NormalCompress" connectionString="...;Initial Catalog=NormalCompress;"/> <add name="EncryptCompress" connectionString="...;Initial Catalog=EncryptCompress;Column Encryption Setting=Enabled;"/> </connectionStrings>
- Tôi đã thực hiện nhiều cải tiến đối với mã C # (xem Phụ lục) dựa trên phản hồi từ tobi (dẫn đến câu hỏi Đánh giá mã này) và một số hỗ trợ tuyệt vời từ đồng nghiệp Brooke Philpott (@Macromullet). Chúng bao gồm:
- loại bỏ thủ tục đã lưu trữ để tạo tên / lương ngẫu nhiên và thực hiện điều đó trong C # thay thế
- sử dụng
Stopwatch
thay vì chuỗi ngày / giờ vụng về - sử dụng
using()
và loại bỏ.Close()
- quy ước đặt tên tốt hơn một chút (và nhận xét!)
- thay đổi
while
vòng lặp đếnfor
vòng lặp - sử dụng
StringBuilder
thay vì nối ngây thơ (mà ban đầu tôi đã chủ ý chọn) - hợp nhất các chuỗi kết nối (mặc dù tôi vẫn đang cố ý tạo một kết nối mới trong mỗi lần lặp lại vòng lặp)
Sau đó, tôi tạo một tệp lô đơn giản sẽ chạy mỗi lần kiểm tra 5 lần (và lặp lại điều này trên cả máy tính cục bộ và máy tính từ xa):
for /l %%x in (1,1,5) do ( ^ AEDemoConsole "Normal" & ^ AEDemoConsole "Encrypt" & ^ AEDemoConsole "NormalCompress" & ^ AEDemoConsole "EncryptCompress" & ^ )
Sau khi các bài kiểm tra hoàn tất, việc đo lường thời lượng và không gian được sử dụng sẽ rất nhỏ (và việc xây dựng biểu đồ từ kết quả sẽ chỉ mất một chút thao tác trong Excel):
-- duration SELECT HostName, Test, AvgInsertTime = AVG(1.0*InsertTime), AvgSelectTime = AVG(1.0*SelectTime) FROM Utility.dbo.Timings GROUP BY HostName, Test ORDER BY HostName, Test; -- space USE Normal; -- NormalCompress; Encrypt; EncryptCompress; SELECT COUNT(*)*8.192 FROM sys.dm_db_database_page_allocations(DB_ID(), OBJECT_ID(N'dbo.Employees'), NULL, NULL, N'LIMITED');
Kết quả thời lượng
Đây là kết quả thô từ truy vấn thời lượng ở trên (CANUCK
là tên của máy lưu trữ phiên bản của SQL Server và HOSER
là máy chạy phiên bản từ xa của mã):
Kết quả thô của truy vấn thời lượng
Rõ ràng là sẽ dễ hình dung hơn ở dạng khác. Như được hiển thị trong biểu đồ đầu tiên, truy cập từ xa có tác động đáng kể đến thời lượng của các lần chèn (tăng hơn 40%), nhưng việc nén có rất ít tác động. Chỉ riêng mã hóa đã tăng gần gấp đôi thời lượng cho bất kỳ danh mục thử nghiệm nào:
Thời lượng (mili giây) để chèn 100.000 hàng
Đối với việc đọc, nén có tác động lớn hơn nhiều đến hiệu suất so với mã hóa hoặc đọc dữ liệu từ xa:
Thời lượng (mili giây) để đọc 100 hàng ngẫu nhiên 1.000 lần
Kết quả khoảng trống
Như bạn có thể đã dự đoán, nén có thể làm giảm đáng kể dung lượng cần thiết để lưu trữ dữ liệu này (gần một nửa), trong khi mã hóa có thể được thấy tác động đến kích thước dữ liệu theo hướng ngược lại (gần gấp ba lần). Và tất nhiên, việc nén các giá trị được mã hóa không mang lại hiệu quả:
Dung lượng được sử dụng (KB) để lưu trữ 100.000 hàng có hoặc không có nén và có hoặc không mã hóa
Tóm tắt
Điều này sẽ cung cấp cho bạn một ý tưởng sơ bộ về tác động sẽ xảy ra khi triển khai Luôn mã hóa. Tuy nhiên, hãy nhớ rằng đây là một thử nghiệm rất cụ thể và tôi đang sử dụng bản dựng CTP ban đầu. Dữ liệu và các mẫu truy cập của bạn có thể mang lại các kết quả rất khác nhau và những tiến bộ hơn nữa trong các CTP trong tương lai và các bản cập nhật cho .NET Framework có thể làm giảm một số khác biệt này ngay cả trong chính thử nghiệm này.
Bạn cũng sẽ nhận thấy rằng kết quả ở đây hơi khác một chút so với trong bài viết trước của tôi. Điều này có thể được giải thích:
- Thời gian chèn nhanh hơn trong mọi trường hợp vì tôi không còn phải mất thêm một chuyến đi vòng quanh cơ sở dữ liệu để tạo tên và mức lương ngẫu nhiên.
- Trong mọi trường hợp, thời gian được chọn nhanh hơn vì tôi không còn sử dụng phương pháp nối chuỗi cẩu thả (đã được đưa vào như một phần của chỉ số thời lượng).
- Không gian được sử dụng lớn hơn một chút trong cả hai trường hợp, tôi nghi ngờ là do sự phân bố khác nhau của các chuỗi ngẫu nhiên đã được tạo.
Phụ lục A - Mã ứng dụng bảng điều khiển C #
using System; using System.Configuration; using System.Text; using System.Data; using System.Data.SqlClient; namespace AEDemo { class AEDemo { static void Main(string[] args) { // set up a stopwatch to time each portion of the code var timer = System.Diagnostics.Stopwatch.StartNew(); // random object to furnish random names/salaries var random = new Random(); // connect based on command-line argument var connectionString = ConfigurationManager.ConnectionStrings[args[0]].ToString(); using (var sqlConnection = new SqlConnection(connectionString)) { // this simply truncates the table, which I was previously doing manually using (var sqlCommand = new SqlCommand("dbo.Cleanup", sqlConnection)) { sqlConnection.Open(); sqlCommand.ExecuteNonQuery(); } } // first, generate 100,000 name/salary pairs and insert them for (int i = 1; i <= 100000; i++) { // random salary between 32750 and 197500 var randomSalary = random.Next(32750, 197500); // random string of random number of characters var length = random.Next(1, 32); char[] randomCharArray = new char[length]; for (int byteOffset = 0; byteOffset < length; byteOffset++) { randomCharArray[byteOffset] = (char)random.Next(65, 90); // A-Z } var randomName = new string(randomCharArray); // this stored procedure accepts name and salary and writes them to table // in the databases with encryption enabled, SqlClient encrypts here // so in a trace you would see @LastName = 0xAE4C12..., @Salary = 0x12EA32... using (var sqlConnection = new SqlConnection(connectionString)) { using (var sqlCommand = new SqlCommand("dbo.AddEmployee", sqlConnection)) { sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.Parameters.Add("@LastName", SqlDbType.NVarChar, 32).Value = randomName; sqlCommand.Parameters.Add("@Salary", SqlDbType.Int).Value = randomSalary; sqlConnection.Open(); sqlCommand.ExecuteNonQuery(); } } } // capture the timings timer.Stop(); var timeInsert = timer.ElapsedMilliseconds; timer.Reset(); timer.Start(); var placeHolder = new StringBuilder(); for (int i = 1; i <= 1000; i++) { using (var sqlConnection = new SqlConnection(connectionString)) { // loop through and pull 100 rows, 1,000 times using (var sqlCommand = new SqlCommand("dbo.RetrieveRandomEmployees", sqlConnection)) { sqlCommand.CommandType = CommandType.StoredProcedure; sqlConnection.Open(); using (var sqlDataReader = sqlCommand.ExecuteReader()) { while (sqlDataReader.Read()) { // do something tangible with the output placeHolder.Append(sqlDataReader[0].ToString()); } } } } } // capture timings again, write both to db timer.Stop(); var timeSelect = timer.ElapsedMilliseconds; using (var sqlConnection = new SqlConnection(connectionString)) { using (var sqlCommand = new SqlCommand("Utility.dbo.AddTiming", sqlConnection)) { sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.Parameters.Add("@Test", SqlDbType.NVarChar, 32).Value = args[0]; sqlCommand.Parameters.Add("@InsertTime", SqlDbType.Int).Value = timeInsert; sqlCommand.Parameters.Add("@SelectTime", SqlDbType.Int).Value = timeSelect; sqlConnection.Open(); sqlCommand.ExecuteNonQuery(); } } } } }