Pourquoi les gens détestent-ils tant les curseurs SQL? [fermé]


127

Je peux comprendre de vouloir éviter d'avoir à utiliser un curseur en raison de la surcharge et des inconvénients, mais il semble qu'il y ait une grave phobie-manie du curseur où les gens se donnent beaucoup de mal pour éviter d'en utiliser un.

Par exemple, une question demandait comment faire quelque chose de évidemment trivial avec un curseur et la réponse acceptée proposée à l'aide d'une requête récursive d'expression de table commune (CTE) avec une fonction personnalisée récursive, même si cela limite le nombre de lignes pouvant être traitées à 32 (en raison de la limite d'appel de fonction récursive dans le serveur SQL). Cela me semble être une solution terrible pour la longévité du système, sans parler d'un effort énorme juste pour éviter d'utiliser un simple curseur.

Quelle est la raison de ce niveau de haine insensée? Une «autorité notoire» a-t-elle émis une fatwa contre les curseurs? Un mal indicible se cache-t-il au cœur des curseurs qui corrompt la morale des enfants ou quelque chose du genre?

Question Wiki, plus intéressée par la réponse que par le représentant.

Informations connexes:

Curseurs d'avance rapide SQL Server

EDIT: permettez-moi d'être plus précis: je comprends que les curseurs ne doivent pas être utilisés à la place des opérations relationnelles normales ; c'est une évidence. Ce que je ne comprends pas, c'est que les gens se mettent en quatre pour éviter les curseurs comme s'ils avaient des cooties ou quelque chose du genre, même lorsqu'un curseur est une solution plus simple et / ou plus efficace. C'est la haine irrationnelle qui me déroute, pas les efficiences techniques évidentes.


1
Je pense que votre Edit dit tout ... Dans presque toutes les situations (que j'ai rencontrées), il existe un moyen de remplacer un curseur par une situation basée sur un ensemble plus performante. Vous dites pas de tête, mais vous comprenez la différence.
StingyJack

7
J'adore les balises sur cette question!
sep332

2
La partie sur les limites récursives de CTE 32est absurde. Vous pensez probablement aux déclencheurs récursifs et au maximum @@NESTLEVELde 32. Il peut être défini dans la requête avec OPTION (MAXRECURSION N)par défaut 100et 0signifiant illimité.
Martin Smith

@MartinSmith: la limite par défaut est maintenant de 100, et le maximum est de 32K sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe

Non, c'est toujours exactement la même chose que lorsque j'ai fait mon commentaire et dans toutes les versions de SQL Server qui prennent en charge les CTE récursifs. Comme votre lien indique "Lorsque 0 est spécifié, aucune limite n'est appliquée".
Martin Smith du

Réponses:


74

La "surcharge" avec les curseurs fait simplement partie de l'API. Les curseurs sont la façon dont certaines parties du SGBDR fonctionnent sous le capot. Souvent CREATE TABLEet INSERTont des SELECTdéclarations, et la mise en œuvre est la mise en œuvre évidente du curseur interne.

L'utilisation d '«opérateurs basés sur des ensembles» de niveau supérieur regroupe les résultats du curseur dans un ensemble de résultats unique, ce qui signifie moins de va-et-vient d'API.

Les curseurs sont antérieurs aux langages modernes qui fournissent des collections de première classe. Les anciens C, COBOL, Fortran, etc., devaient traiter les lignes une par une car il n'y avait pas de notion de «collection» qui pourrait être largement utilisée. Java, C #, Python, etc., ont des structures de liste de première classe pour contenir des ensembles de résultats.

Le problème lent

Dans certains cercles, les jointures relationnelles sont un mystère et les gens écriront des curseurs imbriqués plutôt qu'une simple jointure. J'ai vu des opérations de boucles imbriquées vraiment épiques écrites sous la forme de nombreux curseurs. Vaincre une optimisation SGBDR. Et courir très lentement.

Des réécritures SQL simples pour remplacer les boucles de curseur imbriquées par des jointures et une seule boucle de curseur plat peuvent faire exécuter les programmes 100 fois. [Ils pensaient que j'étais le dieu de l'optimisation. Tout ce que j'ai fait, c'est remplacer les boucles imbriquées par des jointures. Curseurs encore utilisés.]

Cette confusion conduit souvent à une mise en accusation des curseurs. Cependant, ce n'est pas le curseur, c'est la mauvaise utilisation du curseur qui est le problème.

Le problème de la taille

Pour des ensembles de résultats vraiment épiques (c'est-à-dire, vider une table dans un fichier), les curseurs sont essentiels. Les opérations basées sur des ensembles ne peuvent pas matérialiser des ensembles de résultats vraiment volumineux sous la forme d'une seule collection en mémoire.

Alternatives

J'essaie d'utiliser une couche ORM autant que possible. Mais cela a deux objectifs. Tout d'abord, les curseurs sont gérés par le composant ORM. Deuxièmement, le SQL est séparé de l'application dans un fichier de configuration. Ce n'est pas que les curseurs soient mauvais. C'est que coder tous ces ouvre, ferme et récupère n'est pas une programmation à valeur ajoutée.


3
"Les curseurs sont la façon dont le SGBDR fonctionne sous le capot." Si vous voulez dire spécifiquement SQL Server, OK, très bien, j'ignore cela. Mais j'ai travaillé sur les composants internes de plusieurs SGBDR (et ORDBMS) (sous Stonebraker) et aucun d'entre eux ne l'a fait. Exemple: Ingres utilise ce qui équivaut à des "jeux de résultats" de tuples en interne.
Richard T

@Richard T: Je travaille sur des informations de seconde main sur la source du SGBDR; Je vais modifier la déclaration.
S.Lott

2
"J'ai vu des opérations de boucles imbriquées vraiment épiques écrites sous la forme de nombreux curseurs." Je continue de les voir aussi. C'est difficile à croire.
RussellH

41

Les curseurs incitent les gens à appliquer excessivement un état d'esprit procédural à un environnement basé sur des ensembles.

Et ils sont LENTS !!!

De SQLTeam :

Veuillez noter que les curseurs sont le moyen le plus lent d'accéder aux données dans SQL Server. Le ne doit être utilisé que lorsque vous avez vraiment besoin d'accéder à une ligne à la fois. La seule raison à laquelle je pense pour cela est d'appeler une procédure stockée sur chaque ligne. Dans l' article Cursor Performance, j'ai découvert que les curseurs sont plus de trente fois plus lents que les alternatives basées sur des ensembles .


6
cet article a 7 ans, pensez-vous que les choses auraient peut-être changé entre-temps?
Steven A. Lowe

1
Je pense aussi que les curseurs sont vraiment lents et à éviter, en général. Cependant, si l'OP faisait référence à la question que je pense qu'il était, alors un curseur était la bonne solution là-bas (les enregistrements en continu un à la fois en raison de contraintes de mémoire).
rmeador

l'article mis à jour ne corrige pas les mesures de vitesse relative, mais il fournit de bonnes optimisations et alternatives. Notez que l'article original dit que les curseurs sont 50 fois plus rapides que les boucles while, ce qui est intéressant
Steven A. Lowe

6
@BoltBait: Personnellement, je pense que si vous faites des affirmations générales comme ça, vous ne pouvez pas vraiment avoir 45 ans :-P
Steven A. Lowe

4
@BoltBait: Vous, les enfants, sortez de ma pelouse!
Steven A. Lowe

19

Il y a une réponse ci-dessus qui dit que "les curseurs sont le moyen le plus lent d'accéder aux données à l'intérieur de SQL Server ... les curseurs sont plus de trente fois plus lents que les alternatives basées sur des ensembles."

Cette déclaration peut être vraie dans de nombreuses circonstances, mais en tant que déclaration générale, elle est problématique. Par exemple, j'ai fait bon usage des curseurs dans les situations où je souhaite effectuer une opération de mise à jour ou de suppression affectant de nombreuses lignes d'une grande table qui reçoit des lectures de production constantes. L'exécution d'une procédure stockée qui effectue ces mises à jour une ligne à la fois finit par être plus rapide que les opérations basées sur des ensembles, car l'opération basée sur des ensembles entre en conflit avec l'opération de lecture et finit par causer d'horribles problèmes de verrouillage (et peut tuer complètement le système de production, dans des cas extrêmes).

En l'absence d'autres activités de base de données, les opérations basées sur des ensembles sont universellement plus rapides. Dans les systèmes de production, cela dépend.


1
Sonne comme l'exception qui confirme la règle.
Joel Coehoorn

6
@ [Joel Coehoorn]: Je n'ai jamais compris ce dicton.
Steven A. Lowe

2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html comprend l'exception comme "ce qui est laissé de côté" et notez que la règle ici est quelque chose comme "dans la plupart des situations, les curseurs sont mauvais".
David Lay

1
@delm: merci pour le lien, maintenant je comprends encore moins la phrase!
Steven A. Lowe

5
@ [Steven A. Lowe] En gros, cela veut dire que si vous "enfreignez une règle" avec un sous-cas, il doit y avoir une règle générale à enfreindre, par conséquent une règle existe. Par exemple, à partir du lien: ("Si nous avons une déclaration comme 'l'entrée est gratuite le dimanche', nous pouvons raisonnablement supposer qu'en règle générale, l'entrée est facturée.")
Fry

9

Les curseurs ont tendance à être utilisés par les développeurs SQL débutants dans des endroits où les opérations basées sur des ensembles seraient meilleures. En particulier, lorsque les gens apprennent SQL après avoir appris un langage de programmation traditionnel, la mentalité «itérer sur ces enregistrements» a tendance à conduire les gens à utiliser les curseurs de manière inappropriée.

Les livres SQL les plus sérieux incluent un chapitre enjoignant l'utilisation de curseurs; ceux bien écrits indiquent clairement que les curseurs ont leur place mais ne doivent pas être utilisés pour des opérations basées sur des ensembles.

Il y a évidemment des situations où les curseurs sont le bon choix, ou du moins un bon choix.


9

L'optimiseur ne peut souvent pas utiliser l'algèbre relationnelle pour transformer le problème lorsqu'une méthode de curseur est utilisée. Souvent, un curseur est un excellent moyen de résoudre un problème, mais SQL est un langage déclaratif, et il y a beaucoup d'informations dans la base de données, des contraintes, aux statistiques et aux index, ce qui signifie que l'optimiseur a beaucoup d'options pour résoudre le problème, alors qu'un curseur dirige assez explicitement la solution.


8

Dans Oracle PL / SQL, les curseurs n'entraîneront pas de verrous de table et il est possible d'utiliser la collecte en masse / la récupération en masse.

Dans Oracle 10, le curseur implicite souvent utilisé

  for x in (select ....) loop
    --do something 
  end loop;

récupère implicitement 100 lignes à la fois. Une collecte / extraction en bloc explicite est également possible.

Cependant, les curseurs PL / SQL sont quelque chose de dernier recours, utilisez-les lorsque vous ne parvenez pas à résoudre un problème avec SQL basé sur des ensembles.

Une autre raison est la parallélisation, il est plus facile pour la base de données de paralléliser de grandes instructions basées sur des ensembles que du code impératif ligne par ligne. C'est la même raison pour laquelle la programmation fonctionnelle devient de plus en plus populaire (Haskell, F #, Lisp, C # LINQ, MapReduce ...), la programmation fonctionnelle facilite la parallélisation. Le nombre de processeurs par ordinateur augmente donc la parallélisation devient de plus en plus un problème.


6

En général, car sur une base de données relationnelle, les performances du code utilisant des curseurs sont d'un ordre de grandeur pire que les opérations basées sur des ensembles.


avez-vous une référence ou une référence pour cela? je n'ai pas remarqué une telle dégradation drastique des performances ... mais peut-être que mes tables n'ont pas assez de lignes pour que cela compte (un million ou moins, généralement)?
Steven A. Lowe

oh attendez, je vois ce que vous voulez dire - mais je ne recommanderais jamais d'utiliser des curseurs au lieu d'opérations d'ensemble, mais seulement de ne pas aller aux extrêmes pour éviter les curseurs
Steven A. Lowe

3
Je me souviens de la première fois que j'ai fait du SQL, nous avons dû importer un fichier de données quotidien de 50k depuis un mainframe dans une base de données SQL Server ... J'ai utilisé un curseur et j'ai découvert que l'importation prenait environ 26 heures en utilisant le curseur. Lorsque je suis passé aux opérations basées sur les ensembles, le processus a pris 20 minutes.
Charles Bretana

6

Les réponses ci-dessus n'ont pas suffisamment souligné l'importance du verrouillage. Je ne suis pas un grand fan des curseurs car ils entraînent souvent des verrous au niveau de la table.


1
Oui merci! Sans options pour l'empêcher (lecture seule, transfert uniquement, etc.), ils le feront certainement, tout comme toute opération (serveur sql) qui continue à occuper plusieurs lignes, puis plusieurs pages de lignes.
Steven A. Lowe

?? C'est un problème avec votre stratégie de verrouillage PAS les curseurs. Même une instruction SELECT ajoutera des verrous de lecture.
Adam

3

Pour ce que ça vaut, j'ai lu que le "un" place un curseur exécutera son homologue basé sur un ensemble est dans un total courant. Sur une petite table, la vitesse de sommation des lignes sur l'ordre par colonnes favorise l'opération basée sur l'ensemble, mais à mesure que la taille de la table augmente, le curseur deviendra plus rapide car il peut simplement porter la valeur totale en cours au prochain passage du boucle. Maintenant, là où vous devriez faire un total cumulé, c'est un argument différent ...


1
Si vous entendez par «total cumulé» une agrégation d'un type quelconque (min, max, somme), tout SGBD compétent battra le pantalon d'une solution côté client, basée sur le curseur, ne serait-ce que parce que la fonction est exécutée dans le moteur et il n'y a pas de surcharge du client <--> serveur. Peut-être que SQL Server n'est pas compétent?
Richard T

1
@ [Richard T]: nous discutons des curseurs côté serveur, comme dans une procédure stockée, pas des curseurs côté client; Désolé pour la confusion!
Steven A. Lowe


2

En dehors des (non) problèmes de performance, je pense que le plus gros défaut des curseurs est qu'ils sont difficiles à déboguer. Surtout par rapport au code dans la plupart des applications clientes où le débogage a tendance à être relativement facile et les fonctionnalités linguistiques ont tendance à être beaucoup plus faciles. En fait, je soutiens que presque tout ce que l'on fait en SQL avec un curseur devrait probablement se produire dans l'application cliente en premier lieu.


2
SQL est pénible à déboguer, même sans curseurs. Les outils pas à pas MS SQL dans Visual Studio ne semblent pas m'aimer (ils se bloquent beaucoup ou ne déclenchent pas du tout les points d'arrêt), je suis donc généralement réduit aux instructions PRINT ;-)
Steven A. Lowe

1

Pouvez-vous publier cet exemple de curseur ou un lien vers la question? Il existe probablement un moyen encore meilleur qu'un CTE récursif.

En plus d'autres commentaires, les curseurs utilisés de manière incorrecte (ce qui est souvent le cas) provoquent des verrous de page / ligne inutiles.


1
il y a un meilleur moyen - un curseur freakin ';-)
Steven A. Lowe

1

Vous auriez probablement pu conclure votre question après le deuxième paragraphe, plutôt que de qualifier les gens de «fous» simplement parce qu'ils ont un point de vue différent de celui que vous faites et d'essayer de se moquer des professionnels qui peuvent avoir une très bonne raison de ressentir ce qu'ils font.

Quant à votre question, bien qu'il y ait certainement des situations où un curseur peut être appelé, d'après mon expérience, les développeurs décident qu'un curseur "doit" être utilisé LOIN plus souvent que ce n'est le cas en réalité. La chance que quelqu'un se trompe du côté d'une utilisation excessive des curseurs par rapport à ne pas les utiliser quand ils le devraient est BEAUCOUP plus élevée à mon avis.


8
veuillez lire plus attentivement, Tom - la phrase exacte était "haine insensée"; «haï» était l'objet de l'adjectif «insensé», et non «peuple». L'anglais peut parfois être un peu difficile ;-)
Steven A. Lowe

0

fondamentalement 2 blocs de code qui font la même chose. c'est peut-être un exemple un peu étrange mais cela prouve le point. SQL Server 2005:

SELECT * INTO #temp FROM master..spt_values
DECLARE @startTime DATETIME

BEGIN TRAN 

SELECT @startTime = GETDATE()
UPDATE #temp
SET number = 0
select DATEDIFF(ms, @startTime, GETDATE())

ROLLBACK 

BEGIN TRAN 
DECLARE @name VARCHAR

DECLARE tempCursor CURSOR
    FOR SELECT name FROM #temp

OPEN tempCursor

FETCH NEXT FROM tempCursor 
INTO @name

SELECT @startTime = GETDATE()
WHILE @@FETCH_STATUS = 0
BEGIN

    UPDATE #temp SET number = 0 WHERE NAME = @name
    FETCH NEXT FROM tempCursor 
    INTO @name

END 
select DATEDIFF(ms, @startTime, GETDATE())
CLOSE tempCursor
DEALLOCATE tempCursor

ROLLBACK 
DROP TABLE #temp

la mise à jour unique prend 156 ms tandis que le curseur prend 2016 ms.


3
eh bien oui, cela prouve que c'est une façon vraiment stupide d'utiliser un curseur! mais que se passe-t-il si la mise à jour de chaque ligne dépend de la valeur de la ligne précédente dans l'ordre des dates?
Steven A. Lowe

BEGIN TRAN SELECT TOP 1 baseval FROM table ORDER BY timestamp DESC INSERT table (champs) VALUES (valeurs, y compris la valeur dérivée de l'enregistrement précédent) COMMIT TRAN
dkretz

@doofledorfer: cela insérerait une ligne en fonction de la dernière ligne par date, ne mettrait pas à jour chaque ligne par une valeur de sa ligne précédente dans l'ordre des dates
Steven A. Lowe

Pour vraiment utiliser le curseur, vous devez utiliser WHERE CURRENT OF dans la mise à jour
erikkallen
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.