Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

SQL Server 2016:wpływ na wydajność zawsze szyfrowanego

W ramach T-SQL Tuesday #69 pisałem na blogu o ograniczeniach Always Encrypted i wspomniałem tam, że jego użycie może mieć negatywny wpływ na wydajność (jak można się spodziewać, silniejsze zabezpieczenia często mają swoje kompromisy). W tym poście chciałem rzucić okiem na to, pamiętając (ponownie), że te wyniki są oparte na kodzie CTP 2.2, więc bardzo wcześnie w cyklu rozwojowym i niekoniecznie odzwierciedlają wydajność, którą będziesz zobacz przyjdź RTM.

Najpierw chciałem pokazać, że Always Encrypted działa z aplikacji klienckich, nawet jeśli nie jest tam zainstalowana najnowsza wersja SQL Server 2016. Musisz jednak zainstalować podgląd .NET Framework 4.6 (najnowsza wersja tutaj i może się to zmienić), aby obsługiwać Column Encryption Setting atrybut ciągu połączenia. Jeśli używasz systemu Windows 10 lub zainstalowałeś program Visual Studio 2015, ten krok nie jest konieczny, ponieważ powinieneś już mieć wystarczająco najnowszą wersję .NET Framework.

Następnie musisz upewnić się, że certyfikat Always Encrypted istnieje na wszystkich klientach. Jak pokazuje każdy samouczek Always Encrypted, tworzysz klucze szyfrowania głównego i kolumnowego w bazie danych, a następnie musisz wyeksportować certyfikat z tego komputera i zaimportować go na inne, na których będzie uruchamiany kod aplikacji. Otwórz certmgr.msc i rozwiń Certyfikaty — Bieżący użytkownik> Osobiste> Certyfikaty. Powinien tam być jeden o nazwie Always Encrypted Certificate . Kliknij to prawym przyciskiem myszy, wybierz Wszystkie zadania> Eksportuj i postępuj zgodnie z instrukcjami. Wyeksportowałem klucz prywatny i podałem hasło, które wygenerowało plik .pfx. Następnie po prostu powtarzasz odwrotny proces na komputerach klienckich:Otwórz certmgr.msc , rozwiń Certyfikaty — bieżący użytkownik> Osobiste, kliknij prawym przyciskiem myszy Certyfikaty, wybierz Wszystkie zadania> Importuj i wskaż plik .pfx utworzony powyżej. (Oficjalna pomoc tutaj.)

(Istnieją bezpieczniejsze sposoby zarządzania tymi certyfikatami – nie jest prawdopodobne, że po prostu chcesz wdrożyć taki certyfikat na wszystkich komputerach, ponieważ wkrótce zadasz sobie pytanie, o co chodziło? Robiłem to tylko w moim odizolowanym środowisku na potrzeby tego demo – chciałem się upewnić, że moja aplikacja pobiera dane przez sieć, a nie tylko w pamięci lokalnej).

Tworzymy dwie bazy danych, jedną z zaszyfrowaną tabelą, a drugą bez. Robimy to, aby wyizolować ciągi połączeń, a także zmierzyć wykorzystanie przestrzeni. Oczywiście istnieją bardziej szczegółowe sposoby kontrolowania, które polecenia muszą korzystać z połączenia z włączonym szyfrowaniem — zobacz uwagę zatytułowaną „Kontrolowanie wpływu na wydajność…” w tym artykule.

Tabele wyglądają tak:

-- zaszyfrowana kopia, w bazie danych Zaszyfrowana CREATE TABLE dbo.Employees(ID INT IDENTITY(1,1) PRIMARY KEY, LastName NVARCHAR(32) SORTUJ Latin1_General_BIN2 ENCRYPTED WITH (ENCRYPTION_TYPE =DETERMINISTIC, ALGORITHM ='HM_56_SHABCUM56ES) ColumnKey) NIE NULL, Wynagrodzenie INT ZASZYFROWANE Z (ENCRYPTION_TYPE =RANDOMIZED, ALGORYTM ='AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY =ColumnKey) NIE NULL); -- kopia niezaszyfrowana, w bazie danych Normalny CREATE TABLE dbo.Employees( ID INT IDENTITY(1,1) PRIMARY KEY, LastName NVARCHAR(32) COLLATE Latin1_General_BIN2 NOT NULL, Salary INT NOT NULL);

Mając te tabele na miejscu, chciałem skonfigurować bardzo prostą aplikację wiersza poleceń do wykonywania następujących zadań zarówno na zaszyfrowanych, jak i niezaszyfrowanych wersjach tabeli:

  • Wstaw 100 000 pracowników, pojedynczo
  • Przeczytaj 100 losowych wierszy, 1000 razy
  • Wyprowadź sygnatury czasowe przed i po każdym kroku

Mamy więc procedurę składowaną w całkowicie oddzielnej bazie danych, która służy do generowania losowych liczb całkowitych reprezentujących pensje oraz losowych ciągów znaków Unicode o różnych długościach. Zamierzamy to robić pojedynczo, aby lepiej symulować rzeczywiste użycie 100 000 wstawek zachodzących niezależnie (choć nie jednocześnie, ponieważ nie jestem na tyle odważny, aby spróbować poprawnie opracować i zarządzać wielowątkową aplikacją C# lub próbować koordynować i synchronizować wiele instancji pojedynczej aplikacji).

Narzędzie CREATE DATABASE;Narzędzie GO USE;GO CREATE PROCEDURE dbo.GenerateNameAndSalary @Name NVARCHAR(32) OUTPUT, @Salary INT OUTPUTASBEGIN SET NOCOUNT ON; SELECT @ nazwa =LEFT (KONWERSJA (NVARCHAR(32), CRYPT_GEN_RANDOM(64)), RAND() * 32 + 1); SELECT @Wynagrodzenie =CONVERT(INT, RAND()*100000)/100*100;ENDGO

Kilka wierszy przykładowych danych wyjściowych (nie dbamy o rzeczywistą zawartość ciągu, tylko o to, że się zmienia):

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

Następnie procedury składowane, które aplikacja ostatecznie wywoła (są one identyczne w obu bazach danych, ponieważ nie trzeba zmieniać zapytań, aby obsługiwały Always Encrypted):

CREATE PROCEDURE dbo.AddPerson @LastName NVARCHAR(32), @Salary INTASBEGIN SET NOCOUNT ON; INSERT dbo.Employees(Nazwisko, Wynagrodzenie) SELECT @Nazwisko, @Wynagrodzenie;ENDGO CREATE PROCEDURE dbo.RetrievePeopleASBEGIN SET NOCOUNT ON; SELECT TOP (100) ID, Nazwisko, Wynagrodzenie FROM dbo.Pracownicy ORDER BY NEWID();ENDGO

Teraz kod C#, zaczynając od części connectionStrings w App.config. Ważną częścią jest Column Encryption Setting opcja tylko dla bazy danych z zaszyfrowanymi kolumnami (dla zwięzłości załóżmy, że wszystkie trzy parametry połączenia zawierają to samo Data Source i to samo uwierzytelnianie SQL User ID i Password ):

   

I Program.cs (przepraszam, w przypadku takich wersji demonstracyjnych nie potrafię logicznie zmieniać nazw):

przy użyciu System;przy użyciu System.Collections.Generic;przy użyciu System.Text;przy użyciu System.Configuration;przy użyciu System.Data;przy użyciu 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")); nazwa ciągu; string EmptyString =""; wynagrodzenie; int i =1; while (i <=100000) { con1.ConnectionString =ConfigurationManager.ConnectionStrings["Narzędzie"].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.Parametry.Dodaj(n); cmd1.Parametry.Dodaj(e); con1.Open(); cmd1.ExecuteNonQuery(); nazwa =n.Wartość.ToString(); wynagrodzenie =Przelicz.NaInt32(s.Wartość); con1.Zamknij(); } 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.Wartość =nazwa; s.Value =wynagrodzenie; cmd2.Parametry.Dodaj(n); cmd2.Parametry.Dodaj(y); con2.Open(); cmd2.WykonajNonQuery(); con2.Zamknij(); } } i++; } Console.WriteLine(DateTime.UtcNow.ToString("gg:mm:ss.ffffffff")); 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("gg:mm:ss.ffffffff")); } } }}

Następnie możemy wywołać plik .exe za pomocą następujących wierszy poleceń:

AEDemoConsole.exe „Normalny” AEDemoConsole.exe „Szyfruj”

I wygeneruje trzy wiersze danych wyjściowych dla każdego wywołania:czas rozpoczęcia, czas po wstawieniu 100 000 wierszy i czas po 1000 odczytach 100 wierszy. Oto wyniki, które widziałem w moim systemie, uśrednione dla 5 przebiegów każdy:

Czas (w sekundach) zapisywania i odczytywania danych

Zapisywanie danych ma wyraźny wpływ – nie do końca 2X, ale ponad 1,5X. Różnica w odczytywaniu i odszyfrowywaniu danych była znacznie mniejsza – przynajmniej w tych testach – ale to też nie było bezpłatne.

Jeśli chodzi o wykorzystanie miejsca, za przechowywanie zaszyfrowanych danych jest mniej więcej trzykrotna kara (biorąc pod uwagę naturę większości algorytmów szyfrowania, nie powinno to być szokujące). Należy pamiętać, że było to w tabeli z tylko jednym kluczem podstawowym klastra. Oto liczby:

Miejsce (MB) używane do przechowywania danych

Tak więc oczywiście istnieją pewne kary za korzystanie z Always Encrypted, jak zwykle w przypadku prawie wszystkich rozwiązań związanych z bezpieczeństwem (przychodzi mi na myśl powiedzenie „bez darmowego lunchu”). Powtórzę, że te testy zostały przeprowadzone na CTP 2.2, które może radykalnie różnić się od ostatecznej wersji SQL Server 2016. Również te różnice, które zaobserwowałem, mogą odzwierciedlać jedynie charakter testów, które wymyśliłem; oczywiście mam nadzieję, że możesz użyć tego podejścia, aby przetestować swoje wyniki względem schematu, sprzętu i wzorców dostępu do danych.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Znajdź kolumnę partycjonowania dla partycjonowanej tabeli w SQL Server (T-SQL)

  2. Połącz wartości wierszy T-SQL

  3. Prawidłowa metoda usuwania ponad 2100 wierszy (według ID) za pomocą Dapper

  4. Jak naprawić „Powiązana funkcja partycji generuje więcej partycji niż jest grup plików wymienionych w schemacie” Msg 7707 w SQL Server

  5. Najlepszy odpowiednik IsInteger w SQL Server