Database
 sql >> база данни >  >> RDS >> Database

Проста параметризация и тривиални планове — част 2

Типове данни за параметри

Както беше споменато в първата част на тази серия, една от причините да е по-добре да се параметризира изрично е, за да имате пълен контрол върху типовете данни за параметри. Простата параметризация има редица странности в тази област, които могат да доведат до кеширане на повече параметризирани планове от очакваното или до намиране на различни резултати в сравнение с непараметризираната версия.

Когато SQL Server прилага проста параметризация към ad-hoc изявление, той прави предположение за типа данни на параметъра за заместване. Ще разгледам причините за предположението по-късно в поредицата.

За момента нека разгледаме някои примери, използващи базата данни Stack Overflow 2010 на SQL Server 2019 CU 14. Съвместимостта на базата данни е настроена на 150, а прагът на разходите за паралелизъм е настроен на 50, за да се избегне паралелизъм засега:

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;

Тези изявления водят до шест кеширани плана, три Adhoc и три Подготвени :

Различни предполагаеми типове

Обърнете внимание на различните типове данни за параметри в Подготвени планове.

Извод за тип данни

Подробностите за това как се отгатва всеки тип данни са сложни и непълно документирани. Като отправна точка SQL Server извежда основен тип от текстовото представяне на стойността, след което използва най-малкия съвместим подтип.

За низ от числа без кавички или десетична запетая, SQL Server избира от tinyint , smallint и integer . За такива числа извън обхвата на integer , SQL Server използва numeric с възможно най-малка прецизност. Например числото 2,147,483,648 се въвежда като numeric(10,0) . bigint типът не се използва за параметризиране от страна на сървъра. Този параграф обяснява типовете данни, избрани в предишните примери.

Низове от числа с десетичната запетая се интерпретира като numeric , с точност и мащаб, достатъчно голям, за да съдържа предоставената стойност. Низовете с префикс с валутен символ се интерпретират като money . Низовете в научна нотация се превеждат на float . smallmoney и real типове не се използват.

datetime и uniqueidentifer типове не могат да бъдат изведени от естествените низови формати. За да получите datetime или uniqueidentifier тип параметър, стойността на литерала трябва да бъде предоставена в ODBC escape формат. Например {d '1901-01-01'} , {ts '1900-01-01 12:34:56.790'} , или {guid 'F85C72AB-15F7-49E9-A949-273C55A6C393'} . В противен случай предвидената дата или UUID литерал се въвежда като низ. Типове дата и час, различни от datetime не се използват.

Общият низ и двоичните литерали се въвеждат като varchar(8000) , nvarchar(4000) , или varbinary(8000) според случая, освен ако литералът не надвишава 8000 байта, в който случай max се използва вариант. Тази схема помага да се избегне замърсяването на кеша и ниското ниво на повторна употреба, които биха били резултат от използването на специфични дължини.

Не е възможно да се използва CAST или CONVERT за да зададете типа данни за параметри по причини, които ще обясня подробно по-късно в тази серия. Има пример за това в следващия раздел.

Няма да разглеждам принудителното параметризиране в тази серия, но искам да спомена правилата за извеждане на тип данни в този случай имат някои важни разлики в сравнение с простата параметризация . Принудителното параметризиране не беше добавено до SQL Server 2005, така че Microsoft имаше възможността да включи някои уроци от простата параметризация опит и не трябваше да се притеснявате много за проблеми с обратната съвместимост.

Числови типове

За числа с десетична запетая и цели числа извън обхвата на integer , правилата за изведен тип представляват специални проблеми за повторно използване на планове и замърсяване на кеша.

Помислете за следната заявка с десетични знаци:

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;

Тази заявка отговаря на условията за проста параметризация . SQL Server избира най-малката точност и мащаб за параметрите, които могат да съдържат предоставените стойности. Това означава, че избира numeric(8,5) за 987.65432 и numeric(9,3) за 123456.789 :

Изведени числови типове данни

Тези изведени типове не съответстват на decimal(19,8) тип на колоната, така че в плана за изпълнение се появява преобразуване около параметъра:

Преобразуване в тип колона

Тези преобразувания представляват само малка неефективност по време на изпълнение в този конкретен случай. В други ситуации несъответствието между типа данни на колоната и изведения тип на параметър може да попречи на търсене на индекс или да наложи SQL Server да извърши допълнителна работа за създаване на динамично търсене.

Дори когато полученият план за изпълнение изглежда разумен, несъответствието на типа може лесно да повлияе на качеството на плана поради ефекта на несъответствието на типа върху оценката на мощността. Винаги е най-добре да използвате съвпадащи типове данни и да обръщате внимание на извлечените типове, произтичащи от изрази.

Планирайте повторно използване

Основният проблем с текущия план са специфичните изведени типове, засягащи съвпадението на кеширания план и следователно повторното им използване. Нека изпълним още няколко заявки от същата обща форма:

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

Сега погледнете кеша на плана:

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;

Показва AdHoc и Подготвени изявление за всяка заявка, която изпратихме:

Отделни подготвени изявления

Параметризираният текст е същият, но типовете данни за параметрите са различни, така че отделните планове се кешират и не се използва повторно планиране.

Ако продължим да изпращаме заявки с различни комбинации от мащаб или прецизност, нов Подготвен планът ще бъде създаден и кеширан всеки път. Не забравяйте, че изведеният тип на всеки параметър не е ограничен от типа данни на колоната, така че можем да получим огромен брой кеширани планове, в зависимост от представените числови литерали. Броят на комбинациите от numeric(1,0) към numeric(38,38) е вече голям, преди да помислим за множество параметри.

Изрична параметризация

Този проблем не възниква, когато използваме изрично параметризиране, като в идеалния случай избираме същия тип данни като колоната, с която се сравнява параметърът:

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;

С изрична параметризация, заявката за кеширане на плана показва само един кеширан план, използван три пъти и не са необходими преобразувания на типове:

Изрична параметризация

Като последна странична бележка използвах decimal и numeric взаимозаменяеми в този раздел. Те са технически различни типове, въпреки че е документирано, че са синоними и се държат еквивалентно. Това обикновено е така, но не винаги:

-- 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);

Вероятно това е малка грешка в анализатора, но все пак си струва да бъдете последователни (освен ако не пишете статия и искате да посочите интересно изключение).

Аритметични оператори

Има още един краен случай, който искам да разгледам въз основа на пример, даден в документацията, но малко по-подробно (и може би точност):

-- 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;

Резултатите са различни, както е документирано:

Различни резултати

С проста параметризация

Когато просто параметризиране възниква, SQL Server параметризира и двете литерални стойности. 1. стойността се въвежда като numeric(1,0) както се очаква. Донякъде непоследователно, 7 се въвежда като integer (не tinyint ). Правилата за извод на типа са изградени с течение на времето от различни екипи. Поведението се поддържа, за да се избегне нарушаване на наследения код.

Следващата стъпка включва / аритметичен оператор. SQL Server изисква съвместими типове преди да извърши разделянето. Даден е numeric (decimal ) има по-висок приоритет на типа данни от integer , integer ще бъде преобразуван в numeric .

SQL Server трябва имплицитно да преобразува integer към numeric . Но каква прецизност и мащаб да използвате? Отговорът може да се основава на оригиналния литерал, както прави SQL Server при други обстоятелства, но винаги използва numeric(10) тук.

Типът данни на резултата от разделянето на numeric(1,0) чрез numeric(10,0) се определя от друг набор от правила, дадени в документацията за прецизност, мащаб и дължина. Вмъквайки числата във формулите за прецизност на резултата и мащаб, дадени там, имаме:

  • Прецизност на резултата:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + макс(6, 0 + 10 + 1)
    • =1 + макс(6, 11)
    • =1 + 11
    • =12
  • Скала на резултата:
    • max(6, s1 + p2 + 1)
    • =макс(6, 0 + 10 + 1)
    • =макс(6, 11)
    • =11

Типът данни на 1. / 7 следователно е numeric(12, 11) . След това тази стойност се преобразува в float както е поискано и се показва като 0.14285714285 (с 11 цифри след десетичната запетая).

Без проста параметризация

Когато простата параметризация не е извършена, 1. литералът се въвежда като numeric(1,0) по старому. 7 първоначално се въвежда като integer също както се вижда по-рано. Основната разлика е integer се преобразува в numeric(1,0) , така че операторът на разделяне има общи типове, с които да работи. Това е най-малката точност и мащаб, който може да съдържа стойността 7 . Запомнете простата параметризация, използвана numeric(10,0) тук.

Формулите за прецизност и мащаб за разделяне на numeric(1,0) от numeric(1,0) дайте резултатен тип данни numeric(7,6) :

  • Прецизност на резултата:
    • p1 – s1 + s2 + max(6, s1 + p2 + 1)
    • =1 – 0 + 0 + макс(6, 0 + 1 + 1)
    • =1 + макс(6, 2)
    • =1 + 6
    • =7
  • Скала на резултата:
    • max(6, s1 + p2 + 1)
    • =макс(6, 0 + 1 + 1)
    • =макс(6, 2)
    • =6

След окончателното преобразуване в float , показаният резултат е 0.142857 (с шест цифри след десетичната запетая).

Следователно наблюдаваната разлика в резултатите се дължи на извличане на междинен тип (numeric(12,11) спрямо numeric(7,6) ) вместо крайното преобразуване в float .

Ако имате нужда от допълнителни доказателства, преобразуването в float не носи отговорност, имайте предвид:

-- 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);

Резултат с десетичен знак

Резултатите се различават по стойност и мащаб, както преди.

Този раздел не обхваща всяка странност на извеждането и преобразуването на тип данни с просто параметризиране по всякакъв начин. Както беше казано по-рано, по-добре е да използвате изрични параметри с известни типове данни, където е възможно.

Край на част 2

Следващата част от тази серия описва как проста параметризация засяга плановете за изпълнение.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Докоснете и паркирайте:Модел на данни за приложение за паркиране

  2. Тайните на доминото, или модел на данни за играта домино

  3. Как да намерите дублиращи се редове в SQL?

  4. Обединяване на файлове с данни със Statistica, част 2

  5. Качество на данните и размито търсене