なぜ人々はSQLカーソルをそんなに嫌うのですか?[閉まっている]


127

オーバーヘッドと不便さのためにカーソルを使用しなくてもいいようにしたいのは理解できますが、ユーザーがカーソルを使用しなくて済むようにかなりの時間を費やしているところに深刻なカーソル恐怖症が起こっているようです。

たとえば、1つの質問では、カーソルで明らかに取るに足らないことを行う方法と、再帰カスタム関数を使用した共通テーブル式(CTE)再帰クエリを使用して提案された承認済みの回答を質問しましたが、これは処理できる行数を32に制限しています(SQLサーバーの再帰的な関数呼び出し制限のため)。これは、システムの寿命を延ばすための恐ろしい解決策であることに私を襲い、単純なカーソルの使用を避けるための途方もない努力は言うまでもありません。

このレベルの非常識な憎悪の理由は何ですか?いくつかの「注目の権威」はカーソルに対してファトワを発行しましたか?子供たちのモラルを破壊するカーソルの中心に、言い表せない悪が潜んでいますか?

Wikiの質問。担当者より回答に関心があります。

関連情報:

SQL Server早送りカーソル

編集:より正確にしましょう:通常のリレーショナル操作の代わりにカーソルを使用しないでください。それは非常に簡単です。私が理解していないのは、カーソルがより簡単で効率的なソリューションである場合でも、クーディーや何かがあるようなカーソルを回避するために人々が邪魔をしているということです。私を困惑させるのは不合理な憎しみであり、明白な技術的効率ではありません。


1
私はあなたの編集がそれをすべて言っていると思います... ほとんどすべての状況(私が遭遇したこと)で、カーソルをより良いパフォーマンスのセットベースの状況に置き換える方法があります。あなたは非常に簡単と言いますが、あなたは違いを理解しています。
StingyJack 2008年

7
この質問のタグが大好きです。
sep332

2
再帰的なCTE制限についての部分32はナンセンスです。おそらく、あなたは再帰的なトリガーと最大の@@NESTLEVELを考えています32。クエリで設定できOPTION (MAXRECURSION N)、デフォルトで無制限1000意味します。
マーティン・スミス

@MartinSmith:デフォルトの制限は100になり、最大は32Kですsql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe

いいえ、それは私がコメントしたときと、再帰的なCTEをサポートするすべてのバージョンのSQL Serverでまったく同じです。リンクに「0が指定されている場合、制限は適用されません」とあります。
Martin Smith

回答:


74

カーソルを使用した「オーバーヘッド」は、APIの一部にすぎません。カーソルは、RDBMSの一部が内部で機能する方法です。多くの場合CREATE TABLEINSERT持ってSELECT発言をし、実装が明らかに内部のカーソルの実装です。

より高いレベルの「セットベースの演算子」を使用すると、カーソルの結果が単一の結果セットにバンドルされるため、APIのやり取りが少なくなります。

カーソルは、ファーストクラスのコレクションを提供する現代の言語よりも古いものです。古いC、COBOL、Fortranなどは、広く使用できる「コレクション」の概念がないため、一度に1行ずつ処理する必要がありました。Java、C#、Pythonなどには、結果セットを含めるためのファーストクラスのリスト構造があります。

遅い問題

一部のサークルでは、リレーショナル結合が謎であり、人々は単純な結合ではなくネストされたカーソルを書き込みます。私はたくさんのカーソルとして書かれた、本当に壮大な入れ子のループ操作を見てきました。RDBMS最適化を無効にします。そして、本当にゆっくり走っています。

単純なSQLの書き換えにより、ネストされたカーソルループが結合に置き換えられ、単一のフラットカーソルループでプログラムを100倍の時間で実行できます。[彼らは私が最適化の神だと思った。入れ子になったループを結合で置き換えるだけでした。まだ使用されているカーソル。]

この混乱により、カーソルが起訴されることがよくあります。ただし、これはカーソルではなく、カーソルの誤用が問題です。

サイズの問題

本当に壮大な結果セット(つまり、テーブルをファイルにダンプする)の場合、カーソルは不可欠です。セットベースの操作では、非常に大きな結果セットをメモリ内の単一のコレクションとして具体化できません。

代替案

できるだけORMレイヤーを使用するようにしています。しかし、これには2つの目的があります。まず、カーソルはORMコンポーネントによって管理されます。次に、SQLはアプリケーションから構成ファイルに分離されます。カーソルが悪いということではありません。これらすべてのオープン、クローズ、フェッチをコーディングすることは、付加価値プログラミングではないということです。


3
「カーソルは、RDBMSが内部で機能する方法です。」特にSQL Serverのことを言っているのであれば、わかりました。私はそれを知らないのです。しかし、私は複数のRDBMS(およびORDBMS)(Stonebrakerの下)の内部に取り組みましたが、どれもそれを行いませんでした。例:Ingresは、タプルの「結果セット」に相当するものを内部で使用します。
リチャードT

@リチャードT:私はRDBMSソースに関する中古情報を取り扱っています。声明を修正します。
S.Lott、2008

2
「たくさんのカーソルとして書かれた、本当に壮大なネストされたループ操作を見てきました。」私もそれらを見続けます。信じられない。
RussellH 2008

41

カーソルは、手続き型の考え方をセットベースの環境に過度に適用します。

そして、彼らは遅いです!!!

SQLTeamから:

カーソルはSQL Server内のデータにアクセスする最も遅い方法であることに注意してください。は、一度に1つの行に本当にアクセスする必要がある場合にのみ使用してください。そのために考えられる唯一の理由は、各行でストアドプロシージャを呼び出すことです。でカーソルパフォーマンスの記事私はカーソルがあることを発見し30倍遅くセットベースの代替よりもオーバー


6
その記事は7年前のものですが、おそらくその間に状況が変わったのではないかと思いますか?
スティーブンA.ロウ

1
また、カーソルは本当に遅く、一般的には避けるべきだと思います。ただし、OPが彼がそうだと思う質問を参照している場合、カーソルはそこで正しい解決策でした(メモリの制約により、一度に1つずつレコードをストリーミングする)。
rmeador 2008年

更新された記事では相対速度の測定値は修正されていませんが、いくつかの優れた最適化と代替手段が提供されています。元の記事では、カーソルはwhileループよりも50倍高速であると述べています。これは興味深いことです
Steven A. Lowe

6
@BoltBait:私は個人的に、そのような包括的な主張をした場合、実際には45歳になることはできないと思います:-P
Steven A. Lowe

4
@BoltBait:あなたの子供たちは私の芝生を降りる!
スティーブンA.ロウ

19

上記の回答には、「カーソルはSQL Server内のデータにアクセスする最も遅い方法です...カーソルは、セットベースの代替よりも30倍以上遅い」とあります。

このステートメントは多くの状況で当てはまるかもしれませんが、包括的なステートメントとしては問題があります。たとえば、更新または削除操作を実行して、常に本番環境の読み取りを受信して​​いる大きなテーブルの多くの行に影響を与える状況で、カーソルをうまく利用しました。これらの更新を一度に1行ずつ行うストアドプロシージャを実行すると、セットベースの操作よりも高速になります。これは、セットベースの操作が読み取り操作と競合し、恐ろしいロックの問題が発生するためです(そして、運用システムが完全に停止する可能性があります)。極端な場合)。

他のデータベースアクティビティがない場合、セットベースの操作は全体的に高速です。本番システムでは、状況によって異なります。


1
ルールを証明する例外のように聞こえます。
Joel Coehoorn、2008年

6
@ [Joel Coehoorn]:その言葉を理解したことがない。
スティーブンA.ロウ

2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.htmlは、例外を「除外されたもの」として理解し、ここでのルールは「ほとんどの場合、カーソルは悪い"。
デビッドレイ

1
@delm:リンクをありがとう、今ではフレーズの理解度がさらに下がります!
スティーブンA.ロウ

5
@ [Steven A. Lowe]基本的に、サブケースを使用して「ルールを破る」場合、破るための一般的なルールが存在しなければならず、ルールが存在することになります。例:リンクから:(「「日曜日の入場は無料です」のような記述がある場合、原則として入場は有料であると合理的に想定できます。」)
Fry

9

カーソルは、SQL開発の初心者がセットベースの操作のほうが適している場所で使用する傾向があります。特に、従来のプログラミング言語を学んだ後にSQLを学ぶ場合、「これらのレコードを繰り返す」という考え方は、カーソルを不適切に使用する傾向があります。

最も深刻なSQLの本には、カーソルの使用を禁ずる章が含まれています。適切に記述されたものは、カーソルがその場所にあることを明確にしますが、セットベースの操作には使用しないでください。

カーソルが正しい選択、または少なくともAが正しい選択である状況は明らかにあります。


9

カーソルメソッドが使用されている場合、オプティマイザは、関係代数を使用して問題を変換できないことがよくあります。多くの場合、カーソルは問題を解決するための優れた方法ですが、SQLは宣言型言語であり、制約から統計やインデックスまで、データベースには多くの情報が含まれています。つまり、オプティマイザには、問題は、カーソルがほとんど明示的に解決策を指示することです。


8

Oracle PL / SQLでは、カーソルによってテーブルがロックされることはなく、一括収集/一括フェッチを使用できます。

Oracle 10では、よく使用される暗黙カーソル

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

暗黙的に一度に100行をフェッチします。明示的な一括収集/一括フェッチも可能です。

ただし、PL / SQLカーソルは最後の手段の1つです。セットベースのSQLの問題を解決できない場合に使用してください。

もう1つの理由は並列化です。データベースが大きなセットベースのステートメントを並列化するのは、行ごとの命令コードよりも簡単です。関数型プログラミングがますます一般的になるのと同じ理由(Haskell、F#、Lisp、C#LINQ、MapReduce ...)、関数型プログラミングにより並列化が容易になります。コンピューターあたりのCPUの数が増加しているため、並列化がますます問題になっています。


6

一般に、リレーショナルデータベースでは、カーソルを使用したコードのパフォーマンスは、セットベースの操作よりも桁違いに悪いためです。


これのベンチマークまたはリファレンスはありますか?私はそのような劇的なパフォーマンスの低下に気づいていません...しかし、おそらく私のテーブルにはそれが問題となるのに十分な行がありません(通常、100万以下)?
スティーブンA.ロウ

待って、私はあなたが何を意味するのか見てみます-しかし、私はカーソルを避けるために極端に行くのではなく、セット操作の代わりにカーソルを使用することを決して勧めません
スティーブンA.ロウ

3
初めてSQLを実行したときのことを覚えています。メインフレームからSQL Serverデータベースに毎日50kのデータファイルをインポートする必要がありました...カーソルを使用したところ、カーソルを使用してインポートに約26時間かかっていることがわかりました。 。セットベースの操作に変更すると、プロセスに20分かかりました。
Charles Bretana、2008年

6

上記の回答は、ロックの重要性を十分に強調していません。カーソルはテーブルレベルのロックになることが多いため、カーソルの大ファンではありません。


1
はい、ありがとうございます!それを防ぐオプション(読み取り専用、転送のみなど)がない場合、複数の行を占有し、次に複数の行のページを占有する(SQLサーバー)操作と同様に、それらは確実に実行されます。
スティーブンA.ロウ

?? これは、カーソルではなくロック戦略の問題です。SELECTステートメントでも読み取りロックが追加されます。
アダム

3

価値があるのは、カーソルがセットベースの対応を実行する「1つ」の場所が現在の合計であるということです。小さなテーブルでは、列ごとの順序で行を合計する速度はセットベースの操作に有利ですが、テーブルの行サイズが大きくなると、実行中の合計値を次のパスに単純に運ぶことができるため、カーソルが速くなります。ループ。ここで、現在までの合計を実行する場所は別の引数です...


1
何らかの集計(最小、最大、合計)を「合計」することを意味する場合、機能がエンジンで実行されているためにのみ、有能なDBMSは、クライアント側のカーソルベースのソリューションのパンツを打ち負かします。クライアント<->サーバーのオーバーヘッドはありません。たぶんSQL Serverは能力がないのですか?
リチャードT

1
@ [リチャードT]:クライアント側カーソルではなく、ストアドプロシージャ内のサーバー側カーソルについて説明します。混乱させて申し訳ありません!
スティーブンA.ロウ


2

パフォーマンスの(非)問題以外では、カーソルの最大の失敗は、デバッグが難しいことです。特に、デバッグが比較的簡単で、言語機能がはるかに簡単である傾向があるほとんどのクライアントアプリケーションのコードと比較します。実際、SQLでカーソルを使用して行っていることはほとんどすべて、そもそもクライアントアプリで発生しているはずだと私は主張しています。


2
SQLは、カーソルがなくてもデバッグが困難です。私は通常、PRINT文に減少していますので、MS SQLのステップスルーのVisual Studioのツールは、(彼らは多くのことをハングアップする、またはすべてのブレークポイントをトリップしていない)私のようには思えません;-)
スティーブンA. Loweの

1

そのカーソルの例を投稿したり、質問にリンクしたりできますか?おそらく、再帰的なCTEよりも優れた方法があります。

他のコメントに加えて、カーソルを不適切に(多くの場合)使用すると、不要なページ/行ロックが発生します。


1
より良い方法があります-奇抜なカーソル;-)
スティーブンA.ロウ

1

人々があなたとは異なる見方をしているという理由で人々を「狂気」と呼んだり、そうでなければ彼らのやり方を感じるのに十分な理由があるかもしれない専門家をあざけたりしようとするのではなく、おそらく2番目の段落の後にあなたの質問を終えることができます。

あなたの質問については、確かにカーソルが必要になる状況がありますが、私の経験では、開発者はカーソルを実際よりも頻繁に「使用」する必要があると判断しています。私の意見では、誰かがカーソルを使いすぎた場合とそうでない場合のどちらかでエラーが発生する可能性は、はるかに高くなります。


8
トムをもっと注意深く読んでください-正確なフレーズは「非常識な憎悪」でした。「憎まれた」は「人」ではなく形容詞「狂気」の対象でした。英語は時々少し難しいかもしれません;-)
スティーブンA.ロウ

0

基本的に同じことを行う2ブロックのコード。多分それは少し奇妙な例ですが、それは要点を証明しています。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

カーソルが2016ミリ秒かかるのに対し、1回の更新には156ミリ秒かかります。


3
ええ、これは、これがカーソルを使用する本当に馬鹿げた方法であることを証明しています!しかし、各行の更新が日付順に前の行の値に依存している場合はどうなりますか?
スティーブンA.ロウ

BEGIN TRAN SELECT TOP 1 baseval FROMテーブルORDER BY timestamp DESC INSERTテーブル(フィールド)VALUES(vals、前のレコードからの派生値を含む)COMMIT TRAN
dkretz 2008年

@doofledorfer:すべての行を日付順に前の行の値で更新するのではなく、日付ごとに最後の行に基づいて1行を挿入します
Steven A. Lowe

カーソルを本当に使用するには、更新でWHERE CURRENT OFを使用する必要があります
erikkallen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.