オーバーヘッドと不便さのためにカーソルを使用する必要を避けたいということは理解できますが、カーソルを使用しなくても済むように人々がかなりの時間を費やしている深刻なカーソル恐怖症が進行しているようです。
たとえば、ある質問では、カーソルを使用して明らかに些細なことを行う方法を尋ね、一般的な表式(CTE)再帰クエリを使用して再帰カスタム関数を使用して提案された回答を処理しました。 (SQLサーバーでの再帰的な関数呼び出しの制限による)。これは、単純なカーソルの使用を避けるための多大な努力は言うまでもなく、システムの寿命を延ばす恐ろしい解決策だと思います。
このレベルの非常識な憎しみの理由は何ですか?一部の「著名な権限」はカーソルに対してファトワを発行しましたか?言葉では言い表せない悪が、カーソルの中心に潜んでいて、子どもたちや何かのモラルを損なうのでしょうか?
Wikiの質問。担当者よりも回答に興味があります。
関連情報:
編集:より正確にさせてください:通常のリレーショナル操作の代わりにカーソルを使用すべきではないことを理解しています。それは簡単です。私が理解していないのは、カーソルがよりシンプルかつ/またはより効率的なソリューションである場合でも、人がカーソルを避けるように邪魔することです。明らかな技術的効率ではなく、不合理な憎しみが私を困惑させます。
カーソルのある「オーバーヘッド」は、APIの一部にすぎません。カーソルは、RDBMSの一部が内部で機能する方法です。多くの場合、CREATE TABLE
およびINSERT
にはSELECT
ステートメントがあり、実装は明白な内部カーソル実装です。
高レベルの「セットベースの演算子」を使用すると、カーソルの結果が単一の結果セットにバンドルされ、APIのやり取りが少なくなります。
カーソルは、一流のコレクションを提供する最新の言語に先行しています。古いC、COBOL、Fortranなどでは、広く使用できる「コレクション」という概念がなかったため、一度に1行ずつ処理する必要がありました。 Java、C#、Pythonなどには、結果セットを含めるためのファーストクラスのリスト構造があります。
遅い問題
一部のサークルでは、リレーショナル結合は謎であり、人々は単純な結合ではなくネストされたカーソルを作成します。たくさんのカーソルとして書き出される、本当に壮大なネストされたループ操作を見てきました。 RDBMS最適化を無効にします。そして、本当にゆっくり走ります。
ネストされたカーソルループを結合で置き換えるための単純なSQLの書き直しと、単一のフラットカーソルループにより、プログラムを100回実行できます。 [彼らは私が最適化の神だと思っていました。入れ子になったループを結合で置き換えるだけでした。まだ使用されているカーソル。]
この混乱は、多くの場合、カーソルの起訴につながります。ただし、カーソルではなく、カーソルの誤用が問題です。
サイズの問題
本当に壮大な結果セット(つまり、テーブルをファイルにダンプする)には、カーソルが不可欠です。セットベースの操作では、非常に大きな結果セットをメモリ内の単一のコレクションとして具体化できません。
代替案
ORMレイヤーを可能な限り使用するようにします。しかし、それには2つの目的があります。まず、カーソルはORMコンポーネントによって管理されます。第二に、SQLはアプリケーションから構成ファイルに分離されます。カーソルが悪いわけではありません。これらのオープン、クローズ、フェッチをすべてコーディングすることは、付加価値プログラミングではありません。
カーソルを使用すると、手続き型の考え方を過度にセットベースの環境に適用できます。
そして、彼らは遅い!!!
SQLTeam から:
カーソルは、SQL Server内のデータにアクセスする最も遅い方法であることに注意してください。一度に1行にアクセスする必要がある場合にのみ使用してください。そのために考えられる唯一の理由は、各行でストアドプロシージャを呼び出すことです。 Cursor Performanceの記事 で、カーソルがセットベースの代替よりも30倍遅いことを発見しました。
上記の答えは、「カーソルはSQL Server内のデータにアクセスする最も遅い方法です...カーソルはセットベースの代替よりも30倍以上遅い」というものです。
この声明は多くの状況で正しいかもしれませんが、包括的な声明としては問題があります。たとえば、一定の実動読み取りを受信している大きなテーブルの多くの行に影響する更新または削除操作を実行したい状況で、カーソルをうまく利用しました。これらの更新を一度に実行するストアドプロシージャを実行すると、セットベースの操作が読み取り操作と競合し、ひどいロックの問題を引き起こすため、セットベースの操作よりも高速になります極端な場合)。
他のデータベースアクティビティがない場合、集合ベースの操作は普遍的に高速です。実動システムでは、それは依存します。
カーソルは、セットベースの操作が優れている場所でSQL開発者を開始するために使用される傾向があります。特に、人々が伝統的なプログラミング言語を学んだ後にSQLを学ぶとき、「これらの記録を反復する」という考え方は、人々を不適切にカーソルを使用する傾向があります。
最も深刻なSQLブックには、カーソルの使用を禁止する章が含まれています。よく書かれたものは、カーソルに場所があることを明確にしますが、セットベースの操作には使用すべきではありません。
カーソルが正しい選択、または少なくとも正しい選択である状況は明らかにあります。
多くの場合、オプティマイザーはリレーショナル代数を使用して、カーソルメソッドが使用されている場合に問題を変換できません。多くの場合、カーソルは問題を解決するのに最適な方法ですが、SQLは宣言型言語であり、データベースには、制約から統計やインデックスに至るまで多くの情報があります。カーソルはほとんど明示的にソリューションを指示しますが、問題。
Oracle PL/SQLでは、カーソルはテーブルロックにならず、バルク収集/バルクフェッチを使用できます。
Oracle 10では、よく使用される暗黙カーソル
for x in (select ....) loop
--do something
end loop;
一度に暗黙的に100行をフェッチします。明示的なバルク収集/バルクフェッチも可能です。
ただし、PL/SQLカーソルは最後の手段であり、セットベースのSQLで問題を解決できない場合に使用します。
もう1つの理由は並列化です。データベースでは、行ごとの命令コードよりも、大きなセットベースのステートメントを並列化する方が簡単です。関数型プログラミングがますます一般的になるのと同じ理由(Haskell、F#、LISP、C#LINQ、MapReduce ...)で、関数型プログラミングが並列化を容易にします。コンピューターあたりのCPU数が増加しているため、並列化がますます問題になっています。
上記の回答では、ロックの重要性が十分に強調されていません。カーソルはテーブルレベルのロックになることが多いため、私はカーソルの大ファンではありません。
一般に、リレーショナルデータベースでは、カーソルを使用したコードのパフォーマンスは、セットベースの操作よりも桁違いに劣っています。
カーソルがそのセットベースの対応物を実行する「1つの」場所が現在の合計にあることを私が読んだ価値があることについて。小さなテーブルでは、列ごとの順序で行を合計する速度がセットベースの操作を優先しますが、テーブルの行サイズが大きくなると、現在の合計値を次のパスに単純に運ぶことができるため、カーソルが速くなりますループ。 where現在の合計は別の引数です...
パフォーマンス(非)問題以外では、カーソルの最大の失敗は、デバッグするのが苦痛だと思います。特に、デバッグが比較的簡単になり、言語機能がはるかに簡単になる傾向があるほとんどのクライアントアプリケーションのコードと比較して。実際、私は、カーソルを使用してSQLで実行しているほぼすべての処理が、おそらく最初にクライアントアプリで実行されるはずだと主張します。
あなたはおそらく彼らがあなたとは異なる視点を持っているという理由だけで人々を「狂気」と呼ぶのではなく、2番目の段落の後にあなたの質問を結論づけることができたでしょう。
あなたの質問に関して、確かにカーソルが必要になる状況がありますが、私の経験では、開発者はカーソルを実際よりも頻繁に使用する必要があると判断しています。私の意見では、誰かがカーソルを使用しすぎた場合と使用すべきでない場合にカーソルを使用しない場合の可能性は非常に高くなっています。
そのカーソルの例や質問へのリンクを投稿できますか?おそらく、再帰的なCTEよりもさらに良い方法があります。
他のコメントに加えて、カーソルを不適切に使用すると(多くの場合)、不必要なページ/行ロックが発生します。
基本的に同じことを行う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
単一の更新には156ミリ秒かかりますが、カーソルの更新には156ミリ秒かかります。