Database
 sql >> Baza danych >  >> RDS >> Database

Prosta parametryzacja i trywialne plany — część 2

Typy danych parametrów

Jak wspomniano w pierwszej części tej serii, jednym z powodów, dla których lepiej jest jawnie sparametryzować, jest pełna kontrola nad typami danych parametrów. Prosta parametryzacja ma wiele dziwactw w tym obszarze, które mogą skutkować większą liczbą sparametryzowanych planów zapisanych w pamięci podręcznej niż oczekiwano lub znalezieniem innych wyników w porównaniu z wersją niesparametryzowaną.

Kiedy SQL Server stosuje prostą parametryzację do instrukcji ad-hoc, zgaduje typ danych parametru zastępczego. Powody zgadywania omówię w dalszej części serii.

Na razie spójrzmy na kilka przykładów korzystających z bazy danych Stack Overflow 2010 na SQL Server 2019 CU 14. Kompatybilność bazy danych jest ustawiona na 150, a próg kosztów dla równoległości jest ustawiony na 50, aby na razie uniknąć równoległości:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 25221;
GO
SELECT U.DisplayName
FROM dbo.Users AS U 
WHERE U.Reputation = 252552;

Wynikiem tych stwierdzeń jest sześć planów w pamięci podręcznej, trzy Adhoc i trzy Przygotowane :

Różne odgadnięte typy

Zwróć uwagę na różne typy danych parametrów w Przygotowane plany.

Wnioskowanie o typie danych

Szczegóły dotyczące odgadywania każdego typu danych są złożone i niekompletnie udokumentowane. Jako punkt wyjścia, SQL Server wywnioskuje typ podstawowy z tekstowej reprezentacji wartości, a następnie używa najmniejszego zgodnego podtypu.

Dla ciągu liczb bez cudzysłowów lub kropki dziesiętnej, SQL Server wybiera z tinyint , smallint i integer . Dla takich liczb poza zakresem integer , SQL Server używa numeric z najmniejszą możliwą precyzją. Na przykład liczba 2147483648 jest wpisywana jako numeric(10,0) . bigint typ nie jest używany do parametryzacji po stronie serwera. Ten akapit wyjaśnia typy danych wybrane we wcześniejszych przykładach.

Ciągi liczb z kropka dziesiętna jest interpretowana jako numeric , z dokładnością i skalą wystarczająco dużą, aby pomieścić podaną wartość. Ciągi znaków poprzedzone symbolem waluty są interpretowane jako money . Łańcuchy w notacji naukowej tłumaczone są na float . smallmoney i real typy nie są stosowane.

datetime i uniqueidentifer nie można wywnioskować typów z naturalnych formatów ciągów. Aby uzyskać datetime lub uniqueidentifer typ parametru, wartość literału musi być podana w formacie ucieczki ODBC. Na przykład {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} lub {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . W przeciwnym razie zamierzona data lub literał UUID jest wpisywany jako ciąg. Typy daty i godziny inne niż datetime nie są używane.

Ogólne napisy i literały binarne są wpisywane jako varchar(8000) , nvarchar(4000) lub varbinary(8000) odpowiednio, chyba że literał przekracza 8000 bajtów, w którym to przypadku max używany jest wariant. Ten schemat pomaga uniknąć zanieczyszczenia pamięci podręcznej i niskiego poziomu ponownego wykorzystania, który wynikałby z używania określonych długości.

Nie można użyć CAST lub CONVERT aby ustawić typ danych dla parametrów z powodów, które opiszę w dalszej części tej serii. Przykład tego znajduje się w następnej sekcji.

Nie będę omawiać parametryzacji wymuszonej w tej serii, ale chcę wspomnieć o regułach wnioskowania o typach danych w takim przypadku mają pewne istotne różnice w porównaniu z prostą parametryzacją . Wymuszona parametryzacja została dodana dopiero w SQL Server 2005, więc firma Microsoft miała możliwość włączenia kilku lekcji z prostej parametryzacji doświadczenie i nie musiałem się martwić o problemy ze zgodnością wsteczną.

Typy numeryczne

Dla liczb z kropką dziesiętną i liczb całkowitych poza zakresem integer , reguły wywnioskowanego typu stwarzają szczególne problemy związane z ponownym wykorzystaniem planów i zanieczyszczeniem pamięci podręcznej.

Rozważ następujące zapytanie z użyciem ułamków dziesiętnych:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DROP TABLE IF EXISTS dbo.Test;
GO
CREATE TABLE dbo.Test
(
    SomeValue decimal(19,8) NOT NULL
);
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 987.65432 
    AND T.SomeValue < 123456.789;

To zapytanie kwalifikuje się do prostej parametryzacji . SQL Server wybiera najmniejszą precyzję i skalę dla parametrów, które mogą zawierać podane wartości. Oznacza to, że wybiera numeric(8,5) dla 987.65432 i numeric(9,3) dla 123456.789 :

Wnioskowane typy danych liczbowych

Te wywnioskowane typy nie pasują do decimal(19,8) typ kolumny, więc w planie wykonania pojawia się konwersja wokół parametru:

Konwersja na typ kolumny

Te konwersje reprezentują tylko niewielką nieefektywność w czasie wykonywania w tym konkretnym przypadku. W innych sytuacjach niezgodność między typem danych kolumny a wywnioskowanym typem parametru może uniemożliwić wyszukiwanie indeksu lub wymagać od SQL Server wykonania dodatkowej pracy w celu wytworzenia dynamicznego wyszukiwania.

Nawet jeśli wynikowy plan wykonania wydaje się rozsądny, niezgodność typu może łatwo wpłynąć na jakość planu ze względu na wpływ niezgodności typu na oszacowanie kardynalności. Zawsze najlepiej jest używać pasujących typów danych i zwracać szczególną uwagę na typy pochodne wynikające z wyrażeń.

Ponowne wykorzystanie planu

Głównym problemem z bieżącym planem są określone typy wywnioskowane wpływające na dopasowanie planu w pamięci podręcznej, a zatem ponowne użycie. Uruchommy jeszcze kilka zapytań o tej samej formie ogólnej:

SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 98.76 
    AND T.SomeValue < 123.4567;
GO
SELECT 
    T.SomeValue 
FROM dbo.Test AS T 
WHERE 
    T.SomeValue >= 1.2 
    AND T.SomeValue < 1234.56789;
GO

Teraz spójrz na pamięć podręczną planów:

SELECT
    CP.usecounts,
    CP.objtype,
    ST.[text]
FROM sys.dm_exec_cached_plans AS CP
CROSS APPLY sys.dm_exec_sql_text (CP.plan_handle) AS ST
WHERE 
    ST.[text] NOT LIKE '%dm_exec_cached_plans%'
    AND ST.[text] LIKE '%SomeValue%Test%'
ORDER BY 
    CP.objtype ASC;

Pokazuje AdHoc i Przygotowane oświadczenie dla każdego przesłanego zapytania:

Oddzielne przygotowane wyciągi

Sparametryzowany tekst jest taki sam, ale typy danych parametrów są różne, więc oddzielne plany są buforowane i nie następuje ponowne użycie planu.

Jeśli nadal będziemy przesyłać zapytania z różnymi kombinacjami skali lub precyzji, nowy Przygotowany plan będzie tworzony i buforowany za każdym razem. Pamiętaj, że wywnioskowany typ każdego parametru nie jest ograniczony przez typ danych kolumny, więc możemy skończyć z ogromną liczbą buforowanych planów, w zależności od przesłanych literałów liczbowych. Liczba kombinacji z numeric(1,0) na numeric(38,38) jest już duża, zanim pomyślimy o wielu parametrach.

Jasna parametryzacja

Ten problem nie pojawia się, gdy używamy jawnej parametryzacji, najlepiej wybierając ten sam typ danych, co kolumna, z którą porównywany jest parametr:

ALTER DATABASE SCOPED CONFIGURATION 
    CLEAR PROCEDURE_CACHE;
GO
DECLARE 
    @stmt nvarchar(4000) =
        N'SELECT T.SomeValue FROM dbo.Test AS T WHERE T.SomeValue >= @P1 AND T.SomeValue < @P2;',
    @params nvarchar(4000) =
        N'@P1 numeric(19,8), @P2 numeric(19,8)';
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 987.65432, 
    @P2 = 123456.789;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 98.76, 
    @P2 = 123.4567;
 
EXECUTE sys.sp_executesql 
    @stmt, 
    @params, 
    @P1 = 1.2, 
    @P2 = 1234.56789;

W przypadku jawnej parametryzacji zapytanie dotyczące pamięci podręcznej planu pokazuje tylko jeden plan w pamięci podręcznej, używany trzy razy i nie wymaga konwersji typu:

Jawna parametryzacja

Jako ostatnia uwaga, użyłem decimal i numeric zamiennie w tej sekcji. Są technicznie różne typy, choć udokumentowane jako synonimy i zachowujące się równoważnie. Zwykle tak jest, ale nie zawsze:

-- Raises error 8120:
-- Column 'dbo.Test.SomeValue' is invalid in the select list
-- because it is not contained in either an aggregate function
-- or the GROUP BY clause.
SELECT CONVERT(decimal(19,8), T.SomeValue)
FROM dbo.Test AS T 
GROUP BY CONVERT(numeric(19,8), T.SomeValue);

Prawdopodobnie jest to mały błąd parsera, ale nadal opłaca się być spójnym (chyba że piszesz artykuł i chcesz wskazać interesujący wyjątek).

Operatory arytmetyczne

Jest jeszcze jeden przypadek brzegowy, którym chcę się zająć, na podstawie przykładu podanego w dokumentacji, ale bardziej szczegółowo (i być może z dokładnością):

-- The dbo.LinkTypes table contains two rows
 
-- Uses simple parameterization
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization due to
-- constant-constant comparison
SELECT r = CONVERT(float, 1./ 7) 
FROM dbo.LinkTypes AS LT 
WHERE 1 = 1;

Wyniki są różne, zgodnie z dokumentacją:

Różne wyniki

Z prostą parametryzacją

Gdy prosta parametryzacja wystąpi, SQL Server sparametryzuje obie wartości literalne. 1. wartość jest wpisywana jako numeric(1,0) zgodnie z oczekiwaniami. Nieco niespójnie, 7 jest wpisywany jako integer (nie tinyint ). Reguły wnioskowania o typach były budowane z biegiem czasu przez różne zespoły. Zachowania są utrzymywane, aby uniknąć złamania starszego kodu.

Następny krok obejmuje / operator arytmetyczny. SQL Server wymaga zgodnych typów przed wykonaniem podziału. Podano numeric (decimal ) ma wyższy priorytet typu danych niż integer , integer zostanie przekonwertowany na numeric .

SQL Server musi niejawnie przekonwertować integer do numeric . Ale jakiej precyzji i skali użyć? Odpowiedź może być oparta na oryginalnym literale, tak jak SQL Server w innych okolicznościach, ale zawsze używa numeric(10) tutaj.

Typ danych wyniku dzielenia numeric(1,0) za pomocą numeric(10,0) jest określany przez inny zestaw reguł, podanych w dokumentacji, dotyczących dokładności, skali i długości. Wstawiając liczby do podanych tam wzorów na dokładność wyników i skalę, otrzymujemy:

  • Dokładność wyniku:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + maks(6, 0 + 10 + 1)
    • =1 + maks.(6, 11)
    • =1 + 11
    • =12
  • Skala wyniku:
    • maks(6, s1 + p2 + 1)
    • =maks(6, 0 + 10 + 1)
    • =maks(6, 11)
    • =11

Typ danych 1. / 7 jest zatem numeric(12, 11) . Ta wartość jest następnie konwertowana na float na żądanie i wyświetlane jako 0.14285714285 (z 11 cyframi po przecinku).

Bez prostej parametryzacji

Gdy prosta parametryzacja nie jest wykonywana, 1. literał jest wpisywany jako numeric(1,0) jak wcześniej. 7 jest początkowo wpisywany jako integer także jak widzieliśmy wcześniej. Kluczową różnicą jest integer jest konwertowany na numeric(1,0) , więc operator dzielenia ma wspólne typy do pracy. Jest to najmniejsza precyzja i skala, która może zawierać wartość 7 . Pamiętaj, że użyto prostej parametryzacji numeric(10,0) tutaj.

Formuły precyzji i skali do dzielenia numeric(1,0) przez numeric(1,0) podaj typ danych wyniku numeric(7,6) :

  • Dokładność wyniku:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + maks(6, 0 + 1 + 1)
    • =1 + maks.(6, 2)
    • =1 + 6
    • =7
  • Skala wyniku:
    • maks(6, s1 + p2 + 1)
    • =maks(6, 0 + 1 + 1)
    • =maks(6, 2)
    • =6

Po ostatecznej konwersji do float , wyświetlany wynik to 0.142857 (sześć cyfr po przecinku).

Zaobserwowana różnica w wynikach jest zatem spowodowana wyprowadzeniem typu tymczasowego (numeric(12,11) w porównaniu z numeric(7,6) ) zamiast ostatecznej konwersji do float .

Jeśli potrzebujesz dalszych dowodów, konwersja do float nie jest odpowiedzialny, rozważ:

-- Simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT;
 
-- No simple parameterization
SELECT r = CONVERT(decimal(13,12), 1. / 7)
FROM dbo.LinkTypes AS LT 
OPTION (MAXDOP 1);

Wynik z liczbą dziesiętną

Wyniki różnią się wartością i skalą jak poprzednio.

Ta sekcja nie obejmuje wszystkich dziwactw wnioskowania o typach danych i konwersji z prostą parametryzacją dowolnymi środkami. Jak wspomniano wcześniej, lepiej jest używać jawnych parametrów ze znanymi typami danych, gdy tylko jest to możliwe.

Koniec części 2

W następnej części tej serii opisano, jak prosta parametryzacja wpływa na plany wykonania.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kolejny argument za procedurami składowanymi

  2. Znaczenie linii bazowych

  3. Model danych do handlu akcjami, funduszami i kryptowalutami

  4. Złożoność NULL – Część 3, Brakujące standardowe funkcje i alternatywy T-SQL

  5. Samouczek SQL dla początkujących