Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Hiệu suất luôn được mã hóa:Theo dõi

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 đến for 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();
                }
            }
        }
    }
}

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Làm thế nào để lọc các hàng không có NULL trong một cột

  2. Cách bảo vệ ứng dụng JDBC chống lại SQL Injection

  3. Chạy các tác vụ bảo trì cơ sở dữ liệu SQL bằng SQLCMD

  4. Bộ đếm Knee-Jerk PerfMon:Kỳ vọng tuổi thọ của trang

  5. Các nguyên tắc cơ bản về biểu thức bảng, Phần 12 - Hàm nội tuyến được định giá trong bảng