Published on

November 30, 2020

Améliorer les performances des requêtes SQL : Un guide étape par étape

Acquérir de l’expérience dans l’optimisation des requêtes SQL peut être très difficile et compliqué pour les développeurs ou administrateurs de bases de données. Dans cet article, nous travaillerons sur une étude de cas et apprendrons comment améliorer les performances d’une requête étape par étape. En comprenant l’approche pratique des problèmes de performance des requêtes, vous serez en mesure d’améliorer les performances de vos propres requêtes SQL.

Prérequis

Dans cet article, nous utiliserons la base de données d’exemple Adventureworks2017. De plus, nous utiliserons le script Create Enlarged AdventureWorks Tables pour obtenir une version agrandie des tables SalesOrder et SalesOrderDetail. Cela est nécessaire car la taille de la base de données d’origine n’est pas suffisante pour effectuer des tests de performance. Assurez-vous d’installer la base de données Adventureworks2017 et d’exécuter le script d’agrandissement des tables avant de poursuivre l’étude de cas.

Étude de cas : Optimisation des requêtes SQL sans créer un nouvel index

Imaginez que vous êtes employé en tant qu’administrateur de base de données à temps plein dans une entreprise qui utilise encore SQL Server 2017. L’équipe de développement logiciel s’est plainte des performances d’une requête spécifique. Votre objectif est d’améliorer les performances de la requête sans créer un nouvel index sur les tables, mais vous pouvez réécrire la requête.

Jetons un coup d’œil à la requête problématique :

SELECT p.ProductID, p.ProductNumber, p.Name, s.CarrierTrackingNumber, h.AccountNumber, h.CreditCardApprovalCode, dbo.[ufnGetStock](p.ProductID) AS Stock, CONCAT(SUBSTRING(CarrierTrackingNumber, 1, 4), SUBSTRING(p.Class, 1, 4))
FROM Sales.SalesOrderDetailEnlarged s
INNER JOIN Production.Product p ON s.ProductID = p.ProductID
INNER JOIN Sales.SalesOrderHeaderEnlarged h ON h.SalesOrderID = s.SalesOrderID
WHERE s.OrderQty > 2 AND LEN(CreditCardApprovalCode) > 10
ORDER BY CONCAT(SUBSTRING(CarrierTrackingNumber, 1, 4), SUBSTRING(p.Class, 1, 4)), ProductID DESC

La première étape de l’optimisation des requêtes SQL : Identifier les problèmes

La première étape de l’optimisation d’une requête consiste à identifier les problèmes. Activez le plan d’exécution réel dans SSMS et exécutez la requête problématique. Le plan d’exécution réel fournit des statistiques précises et des informations sur la requête. Dans ce cas, la requête a pris 142 secondes pour s’exécuter.

Après avoir analysé le plan d’exécution, nous pouvons identifier plusieurs problèmes :

  • La fonction définie par l’utilisateur (UDF) utilisée dans la requête peut causer un problème de performance.
  • La requête ne génère pas de plan d’exécution parallèle en raison de la présence de fonctions scalaires.
  • La requête rencontre un problème de débordement TempDB.

Améliorer les performances de la fonction scalaire dans la requête

Les fonctions scalaires peuvent être un tueur de performance pour les requêtes. Dans ce cas, la fonction scalaire “ufnGetStock” est invoquée pour chaque ligne de l’ensemble de résultats, ce qui crée un goulot d’étranglement de performance. Pour améliorer les performances, nous pouvons transformer la fonction scalaire en une sous-requête en utilisant l’opérateur CROSS APPLY.

SELECT p.ProductID, p.ProductNumber, p.Name, s.CarrierTrackingNumber, h.AccountNumber, h.CreditCardApprovalCode, Warehouse.Stock AS Stock
FROM Sales.SalesOrderDetailEnlarged s
INNER JOIN Production.Product p ON s.ProductID = p.ProductID
INNER JOIN Sales.SalesOrderHeaderEnlarged h ON h.SalesOrderID = s.SalesOrderID
CROSS APPLY (
    SELECT ISNULL(SUM(Production.[Quantity]), 0) AS Stock
    FROM [Production].[ProductInventory] Production
    WHERE Production.[ProductID] = p.ProductID AND Production.[LocationID] = '6'
) AS Warehouse
WHERE s.OrderQty > 2 AND LEN(CreditCardApprovalCode) > 10
ORDER BY CONCAT(SUBSTRING(CarrierTrackingNumber, 1, 4), SUBSTRING(p.Class, 1, 4)), p.ProductID DESC

En transformant la fonction scalaire en une sous-requête, nous nous assurons que la requête peut s’exécuter en parallèle et sera plus rapide que la version originale.

Faire preuve de créativité pour l’optimisation des requêtes SQL

Pour résoudre le problème de débordement TempDB, nous pouvons créer une table temporaire et y insérer toutes les lignes. De plus, nous pouvons ajouter une colonne calculée au lieu d’utiliser la fonction LEN dans la clause WHERE. Cela permettra une utilisation plus flexible de la table temporaire.

IF OBJECT_ID(N'tempdb..#PerfmonTable') IS NOT NULL
BEGIN
    DROP TABLE #PerfmonTable
END

CREATE TABLE #PerfmonTable (
    [ProductID] [int] NOT NULL,
    [ProductNumber] [nvarchar](25) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [CarrierTrackingNumber] [nvarchar](25) NULL,
    [AccountNumber] [nvarchar](15) NULL,
    [CreditCardApprovalCode] [varchar](15) NULL,
    [Stock] [int] NOT NULL,
    [SortParameter] [nvarchar](6) NOT NULL,
    SmallApp AS ISNULL(LEN(CreditCardApprovalCode), 0) PERSISTED
)

INSERT INTO #PerfmonTable WITH (TABLOCK)
SELECT p.ProductID, p.ProductNumber, p.Name, s.CarrierTrackingNumber, h.AccountNumber, h.CreditCardApprovalCode, Warehouse.Stock, CONCAT(SUBSTRING(CarrierTrackingNumber, 1, 4), SUBSTRING(Class, 1, 4)) AS SortParameter
FROM Sales.SalesOrderDetailEnlarged s
INNER JOIN Production.Product p ON s.ProductID = p.ProductID
INNER JOIN Sales.SalesOrderHeaderEnlarged h ON h.SalesOrderID = s.SalesOrderID
CROSS APPLY (
    SELECT ISNULL(SUM(Production.[Quantity]), 0) AS Stock
    FROM [Production].[ProductInventory] Production
    WHERE Production.[ProductID] = p.ProductID AND Production.[LocationID] = '6'
) AS Warehouse
WHERE s.OrderQty > 2

DELETE FROM #PerfmonTable WHERE SmallApp <= 10

En utilisant une table temporaire et en ajoutant une colonne calculée, nous pouvons éliminer le problème de débordement TempDB. Le temps d’exécution de la requête est considérablement réduit.

Utiliser des index pour améliorer les performances de tri

Lors de la conception d’un index efficace pour les requêtes incluant la clause ORDER BY, nous pouvons éliminer la nécessité d’une opération de tri dans le plan d’exécution. En créant un index non clusterisé qui satisfait les exigences de l’opération de tri, nous pouvons améliorer les performances.

CREATE NONCLUSTERED INDEX IX_Sort ON #PerfmonTable (SortParameter, ProductID DESC) INCLUDE ([ProductNumber], [Name], [CarrierTrackingNumber], [AccountNumber], [CreditCardApprovalCode], Stock)

En créant l’index ci-dessus, nous pouvons éliminer l’opérateur de tri dans le plan d’exécution. Le temps d’exécution de la requête est encore réduit.

Conclusion

Dans cet article, nous avons appris des techniques pratiques pour l’optimisation des requêtes SQL. En abordant le problème de la fonction scalaire, en utilisant des plans d’exécution parallèles et en surmontant les problèmes de débordement TempDB, nous avons pu améliorer considérablement les performances de la requête dans l’étude de

Click to rate this post!
[Total: 0 Average: 0]

Let's work together

Send us a message or book free introductory meeting with us using button below.