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

SQL Server 2016:Tác động đến hiệu suất của Luôn được mã hóa

Là một phần của T-SQL Thứ ba # 69, tôi đã viết blog về những hạn chế của Luôn mã hóa và tôi đã đề cập ở đó rằng hiệu suất có thể bị ảnh hưởng tiêu cực bởi việc sử dụng nó (như bạn có thể mong đợi, bảo mật mạnh hơn thường có sự đánh đổi). Trong bài đăng này, tôi muốn xem nhanh điều này, hãy ghi nhớ (một lần nữa) rằng những kết quả này dựa trên mã CTP 2.2, rất sớm trong chu kỳ phát triển và không nhất thiết phản ánh hiệu suất bạn sẽ xem đến RTM.

Đầu tiên, tôi muốn chứng minh rằng Luôn mã hóa hoạt động từ các ứng dụng khách ngay cả khi phiên bản mới nhất của SQL Server 2016 không được cài đặt ở đó. Tuy nhiên, bạn phải cài đặt bản xem trước .NET Framework 4.6 (phiên bản mới nhất ở đây và có thể thay đổi) để hỗ trợ Column Encryption Setting thuộc tính chuỗi kết nối. Nếu bạn đang chạy Windows 10 hoặc đã cài đặt Visual Studio 2015, thì bước này không cần thiết vì bạn đã có đủ phiên bản .NET Framework gần đây.

Tiếp theo, bạn cần đảm bảo rằng chứng chỉ Luôn được Mã hóa tồn tại trên tất cả các máy khách. Bạn tạo các khóa mã hóa chính và cột trong cơ sở dữ liệu, vì mọi hướng dẫn Luôn được Mã hóa sẽ hiển thị cho bạn, sau đó bạn cần xuất chứng chỉ từ máy đó và nhập chứng chỉ trên các máy khác nơi mã ứng dụng sẽ chạy. Mở certmgr.msc và mở rộng Chứng chỉ - Người dùng hiện tại> Cá nhân> Chứng chỉ và phải có một chứng chỉ ở đó được gọi là Always Encrypted Certificate . Nhấp chuột phải vào đó, chọn Tất cả công việc> Xuất và làm theo lời nhắc. Tôi đã xuất khóa cá nhân và cung cấp mật khẩu tạo ra tệp .pfx. Sau đó, bạn chỉ cần lặp lại quy trình ngược lại trên các máy khách:Mở certmgr.msc , mở rộng Chứng chỉ - Người dùng hiện tại> Cá nhân, nhấp chuột phải vào Chứng chỉ, chọn Tất cả công việc> Nhập và trỏ nó vào tệp .pfx bạn đã tạo ở trên. (Trợ giúp chính thức tại đây.)

(Có nhiều cách an toàn hơn để quản lý các chứng chỉ này - không có khả năng bạn chỉ muốn triển khai chứng chỉ như thế này cho tất cả các máy, vì bạn sẽ sớm tự hỏi bản thân mình xem vấn đề là gì? Tôi chỉ làm điều này trong môi trường cô lập của mình cho mục đích của bản trình diễn này - tôi muốn đảm bảo rằng ứng dụng của tôi đang truy xuất dữ liệu qua dây chứ không chỉ trong bộ nhớ cục bộ.)

Chúng tôi tạo hai cơ sở dữ liệu, một cơ sở dữ liệu có bảng mã hóa và một cơ sở dữ liệu không có. Chúng tôi làm điều này để cô lập các chuỗi kết nối và cũng để đo việc sử dụng không gian. Tất nhiên, có nhiều cách chi tiết hơn để kiểm soát lệnh nào cần sử dụng kết nối hỗ trợ mã hóa - hãy xem ghi chú có tiêu đề "Kiểm soát tác động của hiệu suất…" trong bài viết này.

Các bảng trông như thế này:

-- encrypted copy, in database Encrypted
 
CREATE TABLE dbo.Employees
(
  ID INT IDENTITY(1,1) PRIMARY KEY,
  LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 
    ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC,
	ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
	COLUMN_ENCRYPTION_KEY = ColumnKey) NOT NULL,
  Salary INT
    ENCRYPTED WITH (ENCRYPTION_TYPE = RANDOMIZED,
	ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
	COLUMN_ENCRYPTION_KEY = ColumnKey) NOT NULL
);
 
-- unencrypted copy, in database Normal
 
CREATE TABLE dbo.Employees
(
  ID INT IDENTITY(1,1) PRIMARY KEY,
  LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 NOT NULL,
  Salary INT NOT NULL
);

Với các bảng này tại chỗ, tôi muốn thiết lập một ứng dụng dòng lệnh rất đơn giản để thực hiện các tác vụ sau đối với cả phiên bản được mã hóa và không được mã hóa của bảng:

  • Đưa 100.000 nhân viên vào, mỗi lần một việc
  • Đọc qua 100 hàng ngẫu nhiên, 1.000 lần
  • Dấu thời gian xuất trước và sau mỗi bước

Vì vậy, chúng tôi có một thủ tục được lưu trữ trong một cơ sở dữ liệu hoàn toàn riêng biệt được sử dụng để tạo ra các số nguyên ngẫu nhiên để đại diện cho tiền lương và các chuỗi Unicode ngẫu nhiên có độ dài khác nhau. Chúng tôi sẽ làm điều này mỗi lần một để mô phỏng tốt hơn việc sử dụng thực tế của 100.000 lần chèn diễn ra độc lập (mặc dù không đồng thời, vì tôi không đủ can đảm để cố gắng phát triển và quản lý đúng cách một ứng dụng C # đa luồng hoặc cố gắng điều phối và đồng bộ hóa nhiều phiên bản của một ứng dụng).

CREATE DATABASE Utility;
GO
 
USE Utility;
GO
 
CREATE PROCEDURE dbo.GenerateNameAndSalary
  @Name NVARCHAR(32) OUTPUT,
  @Salary INT OUTPUT
AS
BEGIN
  SET NOCOUNT ON;
  SELECT @Name = LEFT(CONVERT(NVARCHAR(32), CRYPT_GEN_RANDOM(64)), RAND() * 32 + 1);
  SELECT @Salary = CONVERT(INT, RAND()*100000)/100*100;
END
GO

Một vài hàng đầu ra mẫu (chúng tôi không quan tâm đến nội dung thực tế của chuỗi, chỉ là nó thay đổi):

酹2׿ዌ륒㦢㮧羮怰㉤盿⚉嗝䬴敏⽁캘♜鼹䓧
98600
 
贓峂쌄탠❼缉腱蛽☎뱶
72000

Sau đó, các thủ tục được lưu trữ mà ứng dụng cuối cùng sẽ gọi (các thủ tục này giống hệt nhau trong cả hai cơ sở dữ liệu, vì các truy vấn của bạn không cần phải thay đổi để hỗ trợ Luôn mã hóa):

CREATE PROCEDURE dbo.AddPerson
  @LastName NVARCHAR(32),
  @Salary INT
AS
BEGIN
  SET NOCOUNT ON;
  INSERT dbo.Employees(LastName, Salary) SELECT @LastName, @Salary;
END
GO
 
CREATE PROCEDURE dbo.RetrievePeople
AS
BEGIN
  SET NOCOUNT ON;
  SELECT TOP (100) ID, LastName, Salary 
    FROM dbo.Employees
    ORDER BY NEWID();
END
GO

Bây giờ, mã C #, bắt đầu với phần connectionStrings của App.config. Phần quan trọng là Column Encryption Setting tùy chọn chỉ cho cơ sở dữ liệu có các cột được mã hóa (để ngắn gọn, giả sử cả ba chuỗi kết nối đều chứa cùng một Data Source và xác thực SQL giống nhau User IDPassword ):

<connectionStrings>
  <add name="Utility" connectionString="Initial Catalog=Utility;..."/>
  <add name="Normal"  connectionString="Initial Catalog=Normal;..."/>
  <add name="Encrypt" connectionString="Initial Catalog=Encrypted; Column Encryption Setting=Enabled;..."/>
</connectionStrings>

Và Program.cs (xin lỗi, đối với những bản demo như thế này, tôi rất tệ khi truy cập và đổi tên mọi thứ một cách hợp lý):

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
 
namespace AEDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            using (SqlConnection con1 = new SqlConnection())
            {
                Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
                string name;
                string EmptyString = "";
                int salary;
                int i = 1;
                while (i <= 100000)
                {
                    con1.ConnectionString = ConfigurationManager.ConnectionStrings["Utility"].ToString();
                    using (SqlCommand cmd1 = new SqlCommand("dbo.GenerateNameAndSalary", con1))
                    {
                        cmd1.CommandType = CommandType.StoredProcedure;
                        SqlParameter n = new SqlParameter("@Name", SqlDbType.NVarChar, 32) 
                                         { Direction = ParameterDirection.Output };
                        SqlParameter s = new SqlParameter("@Salary", SqlDbType.Int) 
                                         { Direction = ParameterDirection.Output };
                        cmd1.Parameters.Add(n);
                        cmd1.Parameters.Add(s);
                        con1.Open();
                        cmd1.ExecuteNonQuery();
                        name = n.Value.ToString();
                        salary = Convert.ToInt32(s.Value);
                        con1.Close();
                    }
 
                    using (SqlConnection con2 = new SqlConnection())
                    {
                        con2.ConnectionString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
                        using (SqlCommand cmd2 = new SqlCommand("dbo.AddPerson", con2))
                        {
                            cmd2.CommandType = CommandType.StoredProcedure;
                            SqlParameter n = new SqlParameter("@LastName", SqlDbType.NVarChar, 32);
                            SqlParameter s = new SqlParameter("@Salary", SqlDbType.Int);
                            n.Value = name;
                            s.Value = salary;
                            cmd2.Parameters.Add(n);
                            cmd2.Parameters.Add(s);
                            con2.Open();
                            cmd2.ExecuteNonQuery();
                            con2.Close();
                        }
                    }
                    i++;
                }
                Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
                i = 1;
                while (i <= 1000)
                {
                    using (SqlConnection con3 = new SqlConnection())
                    {
                        con3.ConnectionString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
                        using (SqlCommand cmd3 = new SqlCommand("dbo.RetrievePeople", con3))
                        {
                            cmd3.CommandType = CommandType.StoredProcedure;
                            con3.Open();
                            SqlDataReader rdr = cmd3.ExecuteReader();
                            while (rdr.Read())
                            {
                                EmptyString += rdr[0].ToString();
                            }
                            con3.Close();
                        }
                    }
                    i++;
                }
                Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
            }
        }
    }
}

Sau đó, chúng ta có thể gọi .exe bằng các dòng lệnh sau:

AEDemoConsole.exe "Normal"
AEDemoConsole.exe "Encrypt"

Và nó sẽ tạo ra ba dòng đầu ra cho mỗi lần gọi:thời gian bắt đầu, thời gian sau khi 100.000 hàng được chèn và thời gian sau 100 hàng được đọc 1.000 lần. Đây là kết quả tôi thấy trên hệ thống của mình, trung bình hơn 5 lần chạy mỗi lần:

Thời lượng (giây) ghi và đọc dữ liệu

Có một tác động rõ ràng đến việc ghi dữ liệu - không phải là 2X, mà là hơn 1.5X. Khả năng đọc và giải mã dữ liệu thấp hơn nhiều - ít nhất là trong các thử nghiệm này - nhưng điều đó cũng không miễn phí.

Đối với việc sử dụng không gian, có khoảng phạt gấp 3 lần đối với việc lưu trữ dữ liệu được mã hóa (với bản chất của hầu hết các thuật toán mã hóa, điều này không gây sốc). Hãy nhớ rằng điều này nằm trên một bảng chỉ có một khóa chính được nhóm duy nhất. Đây là các số liệu:

Dung lượng (MB) dùng để lưu trữ dữ liệu

Vì vậy, rõ ràng là có một số hình phạt với việc sử dụng Luôn được mã hóa, như thường xảy ra với tất cả các giải pháp liên quan đến bảo mật (câu nói "không có bữa trưa miễn phí" xuất hiện trong tâm trí). Tôi sẽ nhắc lại rằng các thử nghiệm này được thực hiện dựa trên CTP 2.2, có thể khác hoàn toàn so với bản phát hành cuối cùng của SQL Server 2016. Ngoài ra, những khác biệt này mà tôi đã quan sát có thể chỉ phản ánh bản chất của các thử nghiệm mà tôi đã thực hiện; rõ ràng là tôi hy vọng bạn có thể sử dụng phương pháp này để kiểm tra kết quả dựa trên lược đồ, trên phần cứng và với các mẫu truy cập dữ liệu của bạn.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Sử dụng SQL Server làm kho lưu trữ hình ảnh

  2. Chèn một chuỗi với các số 0 ở đầu để nó dài 3 ký tự trong SQL Server 2008

  3. Làm cách nào để có được Chèn id trong MSSQL trong PHP?

  4. Truy vấn T-SQL để hiển thị định nghĩa bảng?

  5. Sử dụng dịch vụ tích hợp máy chủ SQL (SSIS) để điền vào bản ghi QuickBooks