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

Присвояването на входни параметри на съхранена процедура към локални променливи помага ли за оптимизиране на заявката?

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

Представете си таблица (T) с първичен ключ и индексирана колона с дата (A), в таблицата има 1000 реда, 400 имат същата стойност на A (да кажем днес 20130122), останалите 600 реда са следващите 600 дни , така че само 1 запис на дата.

Тази заявка:

SELECT *
FROM T
WHERE A = '20130122';

Ще даде различен план за изпълнение на:

SELECT *
FROM T
WHERE A = '20130123';

Тъй като статистиката ще покаже, че за първите 400 от 1000 реда ще бъдат върнати, оптимизаторът трябва да разпознае, че сканирането на таблица ще бъде по-ефективно от търсенето в отметка, докато второто ще даде само 1 ред, така че търсенето в отметка ще бъде много по-ефективен.

Сега да се върнем към въпроса ви, ако сме направили това процедура:

CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param

След това стартирайте

EXECUTE dbo.GetFromT '20130122'; --400 rows

Планът на заявката със сканирането на таблицата ще бъде използван, ако първия път, когато го стартирате, използвате '20130123' като параметър, той ще съхрани плана за търсене на отметки. Докато процедурата не бъде прекомпилирана, планът ще остане същият. Правете нещо подобно:

CREATE PROCEDURE dbo.GetFromT @Param VARCHAR(5)
AS
    DECLARE @Param2 VARCHAR(5) = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2

След това се изпълнява това:

EXECUTE dbo.GetFromT '20130122';

Докато процедурата се компилира наведнъж, тя не протича правилно, така че планът на заявката, създаден при първата компилация, няма представа, че @Param2 ще стане същото като @param, така че оптимизаторът (без да знае колко реда трябва да очаквам) ще приеме, че ще бъдат върнати 300 (30%), тъй като това ще счита сканирането на таблица за по-ефективно от търсенето в отметка. Ако изпълните същата процедура с „20130123“ като параметър, тя ще доведе до същия план (независимо с какъв параметър е била извикана за първи път), тъй като статистиката не може да се използва за неизвестна стойност. Така че изпълнението на тази процедура за „20130122“ би било по-ефективно, но за всички други стойности би било по-малко ефективно, отколкото без локални параметри (ако приемем, че процедурата без локални параметри е била извикана първо с нещо друго освен „20130122“)

Някои заявки за демонстриране, за да можете сами да видите плановете за изпълнение

Създаване на схема и примерни данни

CREATE TABLE T (ID INT IDENTITY(1, 1) PRIMARY KEY, A DATE NOT NULL, B INT,C INT, D INT, E INT);

CREATE NONCLUSTERED INDEX IX_T ON T (A);

INSERT T (A, B, C, D, E)
SELECT  TOP 400 CAST('20130122' AS DATE), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   type = 'P'
UNION ALL
SELECT TOP 600 DATEADD(DAY, number, CAST('20130122' AS DATE)), number, 2, 3, 4 
FROM    Master..spt_values 
WHERE   Type = 'P';
GO
CREATE PROCEDURE dbo.GetFromT @Param DATE
AS
    SELECT *
    FROM T
    WHERE A = @Param
GO
CREATE PROCEDURE dbo.GetFromT2 @Param DATE
AS
    DECLARE @Param2 DATE = @Param;
    SELECT *
    FROM T
    WHERE A = @Param2
GO

Изпълнение на процедури (показващ действителен план за изпълнение):

EXECUTE GetFromT '20130122';
EXECUTE GetFromT '20130123';
EXECUTE GetFromT2 '20130122';
EXECUTE GetFromT2 '20130123';
GO
EXECUTE SP_RECOMPILE GetFromT;
EXECUTE SP_RECOMPILE GetFromT2;
GO
EXECUTE GetFromT '20130123';
EXECUTE GetFromT '20130122';
EXECUTE GetFromT2 '20130123';
EXECUTE GetFromT2 '20130122';

Ще видите това първия път GetFromT се компилира, използва сканиране на таблица и запазва това, когато се изпълнява с параметъра „20130122“, GetFromT2 също използва сканиране на таблица и запазва плана за '20130122'.

След като процедурите са зададени за повторно компилиране и стартиране отново (забележете в различен ред), GetFromT използва цикъл на отметка и запазва плана за „20130122“, въпреки че преди това е преценил, че сканирането на таблица е по-подходящ план. GetFromT2 не е засегнат от поръчката и има същия план като преди повторното изпълнение.

И така, в обобщение, зависи от разпределението на вашите данни и вашите индекси, вашата честота на повторно компилиране и малко късмет дали дадена процедура ще има полза от използването на локални променливи. Със сигурност не винаги помощ.

Надяваме се, че съм хвърлил малко светлина върху ефекта от използването на локални параметри, планове за изпълнение и компилиране на съхранени процедури. Ако съм се провалил напълно или съм пропуснал ключова точка, тук може да се намери много по-задълбочено обяснение:

http://www.sommarskog.se/query-plan-mysteries.html



  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. SQL - Брои срещанията на конкретна дума във всички съхранени процедури

  3. Какво означава времеви печат в T-Sql в C#?

  4. Как мога да преместя база данни на SQL Server LocalDb от една машина на друга?

  5. tempdb Подобрения в SQL Server 2019