web-dev-qa-db-fra.com

Pourquoi "sélectionner *" plus rapidement que "sélectionner les 500 premiers *" dans SQL Server?

J'ai une vue, complicated_view - il y a quelques jointures et clauses where. Maintenant,

select * from complicated_view (9000 records)

est plus rapide, beaucoup plus rapide, que

select top 500 * from complicated_view

Nous parlons de 19 secondes contre 5+ minutes.

La première requête renvoie tous les 9000 enregistrements. Comment ramasser les 500 premiers ridiculement plus longtemps?

Évidemment, je vais regarder les plans d'exécution ici ---- mais une fois que j'ai compris pourquoi SQL Server exécute le "top 500" de manière sous-optimale, comment puis-je le dire à exécuter le plan rapidement, comme prendre la table complète?

Bien sûr, je devrai peut-être réécrire entièrement la vue --- mais assez étrange.

Fondamentalement, je connecte cette table de données à un logiciel tiers qui pré-vérifie les tables avec une valeur par défaut select top 500 * requête qui ne peut pas être modifiée. Donc, à part jeter cette vue dans un tableau réel (assez bâclé) - je ne peux pas non plus contourner leur addendum "500 premiers".

Il s'agit de SQL Server 2012.

EDIT: pas d'accord sur le drapeau en double. L'autre question, le sommet était PLUS RAPIDE que tout. Ce serait le comportement attendu, renvoyant moins de lignes. Mon cas est le contraire. De plus, je crois comprendre que le Top 100 est un algorithme différent du Top 100+. Je ne pense même pas que la question en double a la bonne réponse. Autrement dit, la requête TOP X TRIERA les tables potentiellement massives très tôt, pas APRÈS qu'elles ne soient agrégées/filtrées/etc. Le pourquoi est un mystère, mais le comment est clairement là.

14
user45867

L'ajout d'une clause TOP à une requête introduit un objectif de ligne à la requête. L'optimiseur de requêtes tentera d'utiliser le fait qu'il n'a pas besoin de renvoyer toutes les lignes pour créer un plan de requête plus efficace. L'objectif de ligne peut entraîner une réduction du coût de certains opérateurs. L'optimisation de l'objectif de ligne peut jouer contre la faveur du tuner de requête en raison des limitations du modèle ou des informations incomplètes dans les objets statistiques. Ci-dessous, j'ai une démo contre une vue simple pour laquelle l'ajout de TOP 500 Dégrade les performances.

Insérez d'abord uniquement des entiers impairs dans une table. Notez que je rassemble des statistiques complètes à la fin.

DROP TABLE IF EXISTS dbo.ODD;

CREATE TABLE dbo.ODD (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(10)
);

INSERT INTO dbo.ODD WITH (TABLOCK)
SELECT TOP (100000)
  -1 + 2 * ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, REPLICATE('FLUFF', 2)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

CREATE STATISTICS S ON dbo.ODD (ID) WITH FULLSCAN;

Insérez ensuite uniquement des entiers pairs dans une table différente. Je fais certaines choses avec des valeurs répétées et la taille des lignes pour faire fonctionner la démo. Je mets toujours à jour les statistiques dans leur intégralité à la fin.

DROP TABLE IF EXISTS dbo.EVEN;

CREATE TABLE dbo.EVEN (
    ID BIGINT NOT NULL,
    FLUFF VARCHAR(3500)
);

INSERT INTO dbo.EVEN WITH (TABLOCK)
SELECT TOP (100000) 
  1000 * FLOOR ( ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) / 500)
, REPLICATE('FLUFF', 700)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2

CREATE STATISTICS S ON dbo.EVEN (ID) WITH FULLSCAN;

Voici la définition de la vue:

CREATE OR ALTER VIEW dbo.TRICKY_VIEW AS
SELECT o.ID
FROM dbo.ODD o
WHERE NOT EXISTS (
    SELECT 1
    FROM dbo.EVEN e WHERE o.ID = e.ID
);

Considérez la requête suivante:

SELECT TOP 500 *
FROM dbo.TRICKY_VIEW
OPTION (MAXDOP 1);

Voici à quoi ressemble le plan de requête:

bad plan

ne limitation des coûts fait que l'analyse complète de la table EVEN sur le côté intérieur de la jointure de boucle imbriquée a un faible coût relatif. D'après la façon dont j'ai construit les données, nous savons que l'optimiseur devra analyser 500 * 100000 = 50 millions de lignes de la table EVEN afin de renvoyer les 500 premières lignes au client. C'est en effet ce qui se passe, et la requête prend environ 16 secondes pour exécuter sur ma machine:

bad actual plan

La suppression de la clause TOP de la requête donne un résultat différent et plus efficace plan :

good plan

Cette requête s'exécute en moins d'une demi-seconde sur ma machine. Seules 100 000 lignes sont lues dans la table EVEN.

Pour SQL Server 2016 et versions ultérieures, vous pouvez contourner ce problème sans modifier la définition de la vue en ajoutant OPTION (USE HINT('DISABLE_OPTIMIZER_ROWGOAL')) à la requête. Cette indication désactive l'optimisation de l'objectif de ligne au niveau de la requête. Pour SQL Server 2012, vous pouvez utiliser l'indicateur de trace 4138 au niveau de la requête via OPTION (QUERYTRACEON 4138), mais cela nécessite SA.

Je ne peux rien dire sur votre requête en particulier sans voir les plans de requête, mais j'espère que cet exemple illustre le point général.

22
Joe Obbish