2010-09-15 7 views
4

J'envisage d'utiliser certains appels de fonction définis par l'utilisateur dans certaines de mes requêtes au lieu d'utiliser un ensemble d'instructions de cas en ligne. Les instructions en ligne fonctionneront probablement mieux, mais les fonctions le rendent beaucoup plus facile à voir et éventuellement à maintenir.Meilleure pratique définie par l'utilisateur

Je voulais juste avoir une idée de ce que sont les meilleures pratiques pour les fonctions UDF? Je réalise que leur utilisation dans les critères (Where Clause) peut avoir des impacts significatifs sur les performances.

Particulièrement dans les cas où vous pouvez avoir beaucoup d'instructions when dans votre bloc case ou même des instructions case imbriquées.

Merci,

S

+0

La meilleure pratique serait de ne pas utiliser des fonctions pour des raisons de sécurité. – Woot4Moo

Répondre

9

Ma réponse en conserve:

Il y a une idée fausse qui ont UDFs effet négatif sur la performance. En tant que déclaration générale, ce n'est tout simplement pas vrai. En fait, les fonctions définies par l'utilisateur (UDF) en ligne sont en fait des macros - l'optimiseur est très capable de réécrire des requêtes les impliquant ainsi que de les optimiser. Cependant, les fonctions UDF scalaires sont généralement très lentes. Je vais donner un petit exemple.

Pré-requis

Voici le script pour créer et remplir les tables:

CREATE TABLE States(Code CHAR(2), [Name] VARCHAR(40), CONSTRAINT PK_States PRIMARY KEY(Code)) 

GO 

INSERT States(Code, [Name]) VALUES('IL', 'Illinois') 

INSERT States(Code, [Name]) VALUES('WI', 'Wisconsin') 

INSERT States(Code, [Name]) VALUES('IA', 'Iowa') 

INSERT States(Code, [Name]) VALUES('IN', 'Indiana') 

INSERT States(Code, [Name]) VALUES('MI', 'Michigan') 

GO 

CREATE TABLE Observations(ID INT NOT NULL, StateCode CHAR(2), CONSTRAINT PK_Observations PRIMARY KEY(ID)) 

GO 

SET NOCOUNT ON 

DECLARE @i INT 

SET @i=0 

WHILE @i<100000 BEGIN 

    SET @i = @i + 1 

    INSERT Observations(ID, StateCode) 

    SELECT @i, CASE WHEN @i % 5 = 0 THEN 'IL' 

    WHEN @i % 5 = 1 THEN 'IA' 

    WHEN @i % 5 = 2 THEN 'WI' 

    WHEN @i % 5 = 3 THEN 'IA' 

    WHEN @i % 5 = 4 THEN 'MI' 

    END 

END 

GO 

Lorsqu'une requête impliquant une UDF est réécrite comme une jointure externe.

Tenir compte de la requête suivante:

SELECT o.ID, s.[name] AS StateName 

    INTO dbo.ObservationsWithStateNames_Join 

    FROM dbo.Observations o LEFT OUTER JOIN dbo.States s ON o.StateCode = s.Code 

/* 

SQL Server parse and compile time: 

    CPU time = 0 ms, elapsed time = 1 ms. 

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Table 'States'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 



SQL Server Execution Times: 

    CPU time = 187 ms, elapsed time = 188 ms. 

*/ 

et le comparer à une requête impliquant une table en ligne évalués UDF:

CREATE FUNCTION dbo.GetStateName_Inline(@StateCode CHAR(2)) 

RETURNS TABLE 

AS 

RETURN(SELECT [Name] FROM dbo.States WHERE Code = @StateCode); 

GO 

SELECT ID, (SELECT [name] FROM dbo.GetStateName_Inline(StateCode)) AS StateName 

    INTO dbo.ObservationsWithStateNames_Inline 

    FROM dbo.Observations 

deux son plan d'exécution et ses coûts d'exécution sont les mêmes - la L'optimiseur l'a réécrit comme une jointure externe. Ne sous-estimez pas la puissance de l'optimiseur!

Une requête impliquant une UDF scalaire est beaucoup plus lent.

Voici une UDF scalaire:

CREATE FUNCTION dbo.GetStateName(@StateCode CHAR(2)) 

RETURNS VARCHAR(40) 

AS 

BEGIN 

    DECLARE @ret VARCHAR(40) 

    SET @ret = (SELECT [Name] FROM dbo.States WHERE Code = @StateCode) 

    RETURN @ret 

END 

GO 

Il est clair que la requête en utilisant cette UDF fournit les mêmes résultats, mais il a un plan d'exécution différent et il est considérablement plus lent:

/* 

SQL Server parse and compile time: 

    CPU time = 0 ms, elapsed time = 3 ms. 

Table 'Worktable'. Scan count 1, logical reads 202930, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Table 'Observations'. Scan count 1, logical reads 188, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 



SQL Server Execution Times: 

    CPU time = 11890 ms, elapsed time = 38585 ms. 

*/ 

Comme vous vu, l'optimiseur peut réécrire et optimiser les requêtes impliquant des UDF de table en ligne. D'autre part, les requêtes impliquant des UDF scalaires ne sont pas réécrites par l'optimiseur - l'exécution de la dernière requête inclut un appel de fonction par ligne, ce qui est très lent. Copié à partir here

+0

Grande réponse, pasib) –

+0

Vous ne devriez jamais interroger à partir de tables dans une fonction scalaire. Période. SQL Server ne les optimise pas du tout. Ils sont même exclus des plans de requête. Vous écrivez littéralement une requête N + 1 dans SQL. N + 1 requêtes sont le diable et ne fonctionnera jamais au fil du temps. – BradLaney

6

Ne pas utiliser les fonctions uniquement par souci d'esthétique. Cela peut être géré en utilisant un format de code cohérent.

Dans la situation que vous décrivez, vous créez une dépendance externe - la fonction doit exister et être visible par l'utilisateur pour que la requête s'exécute. Jusqu'à ce que SQL Server prenne en charge quelque chose d'identique aux packages Oracle (les assemblys ne sont pas natifs SQL) ...

Il y a aussi un risque de tomber dans le piège de croire que les fonctions SQL s'exécutent comme des méthodes/fonctions dans la programmation procédurale/OO, qu'elles ne pas si les requêtes peuvent effectuer pire avec la fonction plutôt que sans.

+0

Merci OMG J'apprécie les commentaires. "récemment basculer à partir d'Oracle" – scarpacci

1

J'ai tendance à éviter la plupart des fonctions car, bien qu'elles soient plus belles que les instructions de cas en ligne, elles tendent également à rendre le plan de requête moins précis. Si vous cachez beaucoup de complexité dans la fonction, la complexité a également tendance à être masquée dans le plan de requête, donc si vous avez des problèmes et que vous avez besoin d'ajuster la requête plus tard, vous corrigerez généralement les problèmes mais sont en réalité un coût trivial par rapport à l'UDF.

+0

cela n'est vrai que pour les UDF scalaires, et complètement faux pour ceux en ligne. –

Questions connexes