web-dev-qa-db-fra.com

Y a-t-il un avantage à SCHEMABINDING une fonction au-delà de la protection Halloween?

Il est bien connu que SCHEMABINDING une fonction peut éviter un spool inutile dans les plans de mise à jour:

Si vous utilisez des UDF T-SQL simples qui ne touchent à aucune table (c'est-à-dire n'accèdent pas aux données), assurez-vous de spécifier l'option SCHEMABINDING lors de la création des UDF. Cela rendra les UDF liés au schéma et garantira que l'optimiseur de requête ne génère aucun opérateur de spoule inutile pour les plans de requête impliquant ces UDF.

Y a-t-il d'autres avantages d'une fonction SCHEMABINDING, même si elle n'accède pas aux données?

53
Paul White 9

Oui.

Ne pas préciser WITH SCHEMABINDING signifie que SQL Server ignore les vérifications détaillées qu'il effectue normalement sur le corps de la fonction. Il marque simplement la fonction comme l'accès aux données (comme mentionné dans le lien donné dans la question).

Il s'agit d'une optimisation des performances. S'il ne faisait pas cette hypothèse, SQL Server devrait effectuer des vérifications détaillées sur chaque appel de fonction (car la fonction non liée pouvait changer à tout moment).

Il existe cinq propriétés de fonction importantes:

  • Déterminisme
  • Précision
  • Accès aux données
  • Accès aux données du système
  • Vérification du système

Par exemple, prenez la fonction scalaire non liée suivante:

CREATE FUNCTION dbo.F
(
    @i integer
)
RETURNS datetime
AS
BEGIN
    RETURN '19000101';
END;

Nous pouvons examiner les cinq propriétés à l'aide d'une fonction de métadonnées:

SELECT 
    IsDeterministic = OBJECTPROPERTYEX(Func.ID, 'IsDeterministic'),
    IsPrecise = OBJECTPROPERTYEX(Func.ID, 'IsPrecise'),
    IsSystemVerified = OBJECTPROPERTYEX(Func.ID, 'IsSystemVerified'),
    UserDataAccess = OBJECTPROPERTYEX(Func.ID, 'UserDataAccess'),
    SystemDataAccess = OBJECTPROPERTYEX(Func.ID, 'SystemDataAccess')
FROM (VALUES(OBJECT_ID(N'dbo.F', N'FN'))) AS Func (ID);

Result

Les deux propriétés d'accès aux données ont été définies sur true et les trois autres sont définies sur false .

Cela a des implications au-delà de celles auxquelles on peut s'attendre (utilisation dans des vues indexées ou des colonnes calculées indexées, par exemple).

Effets sur l'optimiseur de requêtes

La propriété Déterminisme affecte en particulier l'optimiseur de requête. Il a des règles détaillées concernant les types de réécritures et de manipulations qu'il est autorisé à effectuer, et celles-ci sont très restreintes pour les éléments non déterministes. Les effets secondaires peuvent être assez subtils.

Par exemple, considérez les deux tableaux suivants:

CREATE TABLE dbo.T1
(
    SomeInteger integer PRIMARY KEY
);
GO
CREATE TABLE dbo.T2
(
    SomeDate datetime PRIMARY KEY
);

... et une requête qui utilise la fonction (telle que définie précédemment):

SELECT * 
FROM dbo.T1 AS T1
JOIN dbo.T2 AS T2
    ON T2.SomeDate = dbo.F(T1.SomeInteger);

Le plan de requête est comme prévu, avec une recherche dans le tableau T2:

Seek plan

Cependant, si la même requête logique est écrite à l'aide d'une table dérivée ou d'une expression de table commune:

WITH CTE AS
(
    SELECT *, dt = dbo.F(T1.SomeInteger) 
    FROM dbo.T1 AS T1
)
SELECT * 
FROM CTE
JOIN dbo.T2 AS T2
    ON T2.SomeDate = CTE.dt;

-- Derived table
SELECT
    *
FROM 
(
    SELECT *, dt = dbo.F(T1.SomeInteger)
    FROM dbo.T1 AS T1
) AS T1
JOIN dbo.T2 AS T2
    ON T2.SomeDate = T1.dt;

Le plan d'exécution comporte désormais une analyse, le prédicat impliquant la fonction étant coincé dans un filtre:

Scan plan

Cela se produit également si la table dérivée ou l'expression de table commune est remplacée par une vue ou une fonction en ligne. Un indice FORCESEEK (et d'autres tentatives similaires) échouera:

Error message

Le problème fondamental est que l'optimiseur de requête ne peut pas réorganiser les éléments de requête non déterministes aussi librement .

Pour produire une recherche, le prédicat Filter devrait être déplacé vers le bas du plan vers l'accès aux données T2. Ce mouvement est empêché lorsque la fonction n'est pas déterministe.

Réparer

Le correctif de cet exemple implique deux étapes:

  1. Ajouter WITH SCHEMABINDING
  2. Rendre la fonction déterministe

La première étape est triviale. La seconde consiste à supprimer la conversion implicite non déterministe de la chaîne en datetime; le remplacer par un CONVERT déterministe. Ni l'un ni l'autre ne suffit à lui seul .

ALTER FUNCTION dbo.F
(
    @i integer
)
RETURNS datetime
WITH SCHEMABINDING
AS
BEGIN
    -- Convert with a deterministic style
    RETURN CONVERT(datetime, '19000101', 112);
END;

Les propriétés de la fonction sont désormais:

New properties

Avec l'optimiseur libéré, tous les exemples produisent maintenant le plan de recherche souhaité .


Notez que l'utilisation d'un CAST à datetime dans la fonction ne fonctionnerait pas, car il n'est pas possible de spécifier un style de conversion dans cette syntaxe:

ALTER FUNCTION dbo.F
(
    @i integer
)
RETURNS datetime
WITH SCHEMABINDING
AS
BEGIN
    -- Convert with a deterministic style
    RETURN CAST('19000101' AS datetime);
END;

Cette définition de fonction produit le plan d'analyse, et les propriétés montrent qu'il reste non déterministe:

CAST function properties

81
Paul White 9