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

SQL Server:Ciemna strona NVARCHAR

Wprowadzenie

W tym artykule omówimy używanie nvarchar typ danych. Zbadamy, jak SQL Server przechowuje ten typ danych na dysku i jak jest przetwarzany w pamięci RAM. Zbadamy również, jak rozmiar nvarchar może wpływać na wydajność.

Rzeczywisty rozmiar danych:nchar vs nvarchar

Używamy nvarchar kiedy rozmiar wpisów danych w kolumnie prawdopodobnie będzie się znacznie różnić. Rozmiar pamięci (w bajtach) jest dwukrotnie większy od rzeczywistej długości wprowadzonych danych + 2 bajty. Pozwala nam to zaoszczędzić miejsce na dysku w porównaniu z użyciem nchar typ danych. Rozważmy następujący przykład. Tworzymy dwie tabele. Jedna tabela zawiera kolumnę nvarchar, inna tabela zawiera kolumny nchar. Rozmiar kolumny to 2000 znaków (4000 bajtów).

CREATE TABLE dbo.testnvarchar (
  col1 NVARCHAR(2000) NULL
);
GO

INSERT INTO dbo.testnvarchar (col1)
  SELECT
    REPLICATE('&', 10)
GO 

CREATE TABLE dbo.testnchar (
  col1 NCHAR(2000) NULL
);
GO

INSERT INTO dbo.testnchar (col1)
  SELECT
    REPLICATE('&', 10)
GO 

Rzeczywisty rozmiar wiersza to:

Jak widać, rzeczywisty rozmiar wiersza typu danych nvarchar jest znacznie mniejszy niż typ danych nchar. W przypadku typu danych nchar używamy ~4000 bajtów do przechowywania 10 symboli ciągu znaków. Używamy ~20 bajtów do przechowywania tego samego ciągu znaków w przypadku typu danych nvarchar.

Silnik SQL Server przetwarza dane w pamięci RAM (pula buforów). A co z rozmiarem wiersza w pamięci?

Rzeczywisty rozmiar danych:HDD vs RAM

Wykonajmy następujące zapytanie:

SELECT col1 FROM dbo.testnchar;

Nie ma różnicy między wykorzystaniem dysku i pamięci RAM w przypadku ciągu znaków o stałej długości.

SELECT col1 FROM dbo.testnvarchar;

Widzimy, że silnik SQL Server zażądał pamięci tylko dla połowy zadeklarowanego rozmiaru wiersza (2000 bajtów zamiast rzeczywistych 20 bajtów) i kilku bajtów na dodatkowe informacje. Z jednej strony zmniejszamy wykorzystanie miejsca na dysku, ale z drugiej możemy nadmuchać żądaną pamięć RAM. Jest to efekt uboczny używania różnych typów danych znaków. Ten efekt uboczny może w niektórych przypadkach mieć duży wpływ na zasoby.

FORMAT():Żądana pamięć RAM a wykorzystana pamięć RAM

Używamy funkcji FORMAT, która zwraca sformatowaną wartość z określonym formatem i opcjonalną kulturą. Zwracana wartość to nvarchar lub null. Długość zwracanej wartości jest określona przez format . FORMAT(getdate(), „rrrrMMdd”, „en-US”) da wynik „20170412”. Potrzebujemy 16 bajtów, aby zapisać ten wynik w kolumnie na dysku (wynikiem będzie nvarchar(8)). Jaki jest rozmiar danych w pamięci RAM dla poszczególnych danych?

Wykonajmy następujące zapytanie. Używamy następującego środowiska:

  • AdventureWorks2014
  • Wersja rozwojowa MS SQL 2016
  • dbo.Klient (19 820 000 rekordów) zawiera dane ze sprzedaży.Klient (19 820 rekordów przesłano 1000 razy)):
;WITH rs
AS
(SELECT
    c.customerid
   ,c.modifieddate
   ,p.LastName
  FROM [dbo].[Customer] c
  LEFT OUTER JOIN [person].[person] p
    ON p.BusinessEntityID = c.PersonID)
SELECT
  customerid
 ,LastName
 ,FORMAT([modifieddate], 'yyyyMMdd', 'en-US') AS md
 ,' ' AS code INTO #tmp
FROM rs

Plan wykonania zapytania jest dość prosty:

Pierwsza operacja to „Skanowanie indeksu klastrowego” w tabeli dbo.Customer. Przeczytano ~19 000 000 rekordów. Szacowany rozmiar danych to 435 MB.

Następna operacja to „Oblicz skalar” (obliczanie funkcji FORMAT()). Wynik jest dość nieoczekiwany, ponieważ formatujemy 16-bajtowy ciąg znaków. Rozmiar wiersza drastycznie wzrósł z 23 bajtów do 4019 bajtów. To samo z szacowanym rozmiarem danych — od 435 MB do 74 GB. Widzimy, że FORMAT() zwraca NVARCHAR(4000).

MS SQL Server 2016 ma świetną zdolność do pokazywania nadmiernego przydziału pamięci. Widzimy ostrzeżenie w ostatniej operacji (T-SQL SELECT INTO):

Jest to „nadmierna ilość” pamięci:ponad 90% przyznanej pamięci nie jest używane.

Statystyki czasu zapytania to:

Długi czas wykonania zależy od nieefektywnego wykonania funkcji skalarnej i efektu wstecznego przyznania nadmiernej pamięci – Hash Match (Right Outer Join). Mamy skumulowany efekt dwóch różnych przyczyn:wielokrotne wykonywanie funkcji skalarnych i nadmierne przydzielanie pamięci.

Aparat SQL Server może przyznać nie więcej niż 25% dozwolonej pamięci na zapytanie. Wartość tę możemy zmienić w wersji Enterprise serwera MS SQL Server za pomocą gubernatora zasobów. Przyznana pamięć składa się z dwóch części:wymaganej i dodatkowej. Wymagana pamięć jest wykorzystywana na potrzeby wewnętrzne – do sortowania i operacji łączenia mieszającego. Dodatkowa pamięć jest oparta na szacowanym rozmiarze danych. Jeśli zarówno wymagana, jak i dodatkowa pamięć przekracza limit 25%, silnik SQL Server przyznaje kolejne 25% dostępnej pamięci. Przeczytaj wpis o przyznaniu pamięci SQL Server, aby uzyskać szczegółowe informacje.

Wykonajmy to samo zapytanie bez funkcji FORMAT().

;WITH rs
AS
(SELECT
    c.customerid
   ,c.modifieddate
   ,p.LastName
  FROM [dbo].[Customer] c
  LEFT OUTER JOIN [person].[person] p
    ON p.BusinessEntityID = c.PersonID)
SELECT
  customerid
 ,LastName
 ,' ' AS code INTO #tmp
FROM rs

Możemy zobaczyć kolejną implementację Right Outer Join (Merge Join zamiast Hash Join).

Informacja o przyznaniu pamięci to (jeśli nie sortowanie i Hash Join SQL Server nie może przyznać pamięci):

Czas zapytania Statystyki to (czas jest przewidywalnie skrócony:brak wykonania funkcji skalarnej, szacowany rozmiar danych jest mniejszy niż w poprzedniej próbce):

Tak więc zwiększamy „przyznaną pamięć” do 222 MB (i zużywamy mniej niż 2 MB) za pomocą funkcji FORMAT(). Ilość danych w przykładzie jest niewielka.

Zapytanie o wykonanie długiego czasu

Rozważ prawdziwe zapytanie SQL ze środowiska produkcyjnego. To zapytanie zostało wykonane podczas procesu ładowania wsadowego (nie w klasycznym scenariuszu transakcyjnym). Korzystamy z MS SQL Server uruchomionego na Amazon Web Services (AWS, Amazon Relational Database Service). Charakterystyki instancji DB to 160 GB pamięci RAM (nie więcej niż ~30 GB pamięci RAM można przyznać na zapytanie) i 40 vCPU. Zapytanie SQL było prawie takie samo jak w powyższym przykładzie (różnica polega na ilości tabel i wielkości danych):CTE zawierało łączenie między 6 tabelami. „Tabela główna” (tabela w klauzuli FROM) zawiera ~175 000 000 rekordów, a rozmiar danych wynosi 20 GB. Tabele przeglądowe (prawa tabela w klauzuli JOIN) są małe (w porównaniu z tabelą główną). Zapytanie SQL zawiera dwa wywołania funkcji FORMAT() (parametrem tej funkcji są dwie kolumny z tabeli „tabela główna”).

Zapytanie produkcyjne wygląda tak:

;WITH rs AS
(
SELECT 
<in column list>,
c.modifieddate, 
c.createddate
FROM [Master table] c
	LEFT OUTER JOIN [table1 ] p1 ON …
	LEFT OUTER JOIN [table2 ] p2 ON …
	LEFT OUTER JOIN [table3 ] p3 ON …
	LEFT OUTER JOIN [table4 ] p4 ON …
	LEFT OUTER JOIN [table5 ] p5 ON …
)
SELECT DISTINT
<out column list>,
FORMAT([modifieddate], 'yyyyMMdd','en-US') AS md,
FORMAT([createddate], 'yyyyMMdd','en-US') AS cd
INTO #tmp
FROM rs

„Obraz” planu wykonania znajduje się poniżej (plan wykonania jest prosty:łączenia sekwencyjne i sortowanie (słowa kluczowe DISTINCT) na górze):

Pozwól nam szczegółowo zbadać informacje.

Pierwsza operacja to „Skanowanie tabeli” (wszystko się zgadza, bez niespodzianek):

Operacja „Obliczenia skalarne” radykalnie zwiększa szacowany rozmiar wiersza, a także szacowany rozmiar wiersza (z 19 GB do 1,3 TB). Dwa wywołania funkcji FORMAT() dodały około 8000 bajtów do szacowanego rozmiaru wiersza (ale rzeczywisty rozmiar danych jest mniejszy).

Jedna z operacji JOIN (Hash Match, Right Outer Join) używa nieunikatowych kolumn z prawej tabeli. W przypadku kilku płyt nie ma to znaczenia. To nie jest nasz przypadek. W rezultacie szacowany rozmiar danych wzrasta do ~2,4 TB.

Pojawia się również ostrzeżenie (brak wystarczającej ilości pamięci RAM do przetworzenia tej operacji):

Zapytanie SQL zawiera na górze operację „Distinct Sort”, która wygląda jak wisienka na torcie. Widzimy tam to samo ostrzeżenie.

Efektem użycia funkcji skalarnej jest długi czas wykonania zapytania:24 godziny. Jedną z przyczyn tego problemu jest nieprawidłowe oszacowanie żądanego rozmiaru danych na podstawie „Szacowanego rozmiaru danych”. Bez użycia funkcji FORMAT(), MS SQL Server wykonuje to zapytanie w ciągu 2 godzin.

Wniosek

Deweloperzy powinni zachować ostrożność podczas używania typów danych nvarchar i varchar. Wybór redundantnych typów danych dla kolumn może prowadzić do zawyżenia wymaganej pamięci. W rezultacie pamięć RAM zostanie zmarnowana, a wydajność bazy danych spadnie.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Uzyskaj AVG ignorując wartości Null lub Zero

  2. SYSUTCDATETIME() Przykłady w SQL Server (T-SQL)

  3. Hash md5 TSQL różni się od C# .NET md5

  4. Jak przenosić pliki danych w SQL Server — część 1

  5. Sprawdź nieudane wiadomości e-mail w programie SQL Server (T-SQL)