SQL Server でカーソルを使用することが悪い習慣であると考えられるのはなぜですか?

StackOverflow https://stackoverflow.com/questions/58141

質問

SQL 7 時代にパフォーマンス上の理由がいくつかあることは知っていましたが、同じ問題が SQL Server 2005 にもまだ存在するのでしょうか?ストアド プロシージャ内に個別に操作したい結果セットがある場合、やはりカーソルは悪い選択でしょうか?もしそうなら、なぜですか?

役に立ちましたか?

解決

カーソルはメモリを占有し、ロックを作成するためです。

あなたが実際にやっていることは、セットベースのテクノロジーを非セットベースの機能に強制的に組み込もうとしていることです。公平を期して言っておきますが、カーソルは する 使い方はありますが、セットベースのソリューションの使用に慣れていない多くの人が、セットベースのソリューションを理解する代わりにカーソルを使用するため、眉をひそめています。

ただし、カーソルを開くと、基本的にそれらの行がメモリにロードされてロックされ、潜在的なブロックが作成されます。次に、カーソルを循環させると、他のテーブルに変更を加えながら、カーソルのすべてのメモリとロックが開いたままになります。

これらはすべて、他のユーザーにパフォーマンスの問題を引き起こす可能性があります。

したがって、一般的なルールとして、カーソルは嫌われます。それが問題を解決する際に最初に到達した解決策である場合は特にそうです。

他のヒント

SQL がセットベースの環境であるという上記のコメントはすべて真実です。ただし、行ごとの操作が役立つ場合もあります。メタデータと動的 SQL の組み合わせを検討してください。

非常に単純な例として、コピー/切り捨てなどを行うテーブルの名前を定義するテーブル内に 100 以上のレコードがあるとします。どれが一番いいですか?必要なことを行うために SQL をハードコーディングしますか?それとも、この結果セットを反復処理し、動的 ​​SQL (sp_executesql) を使用して操作を実行しますか?

セットベースの SQL を使用して上記の目的を達成する方法はありません。

それでは、カーソルまたは while ループ (疑似カーソル) を使用するにはどうすればよいでしょうか?

SQL カーソルは、正しいオプションを使用している限り問題ありません。

INSENSITIVE は結果セットの一時コピーを作成します (擬似カーソルに対して自分でこれを行う必要がなくなります)。

READ_ONLY は、基になる結果セットにロックが保持されていないことを確認します。基になる結果セットの変更は、後続のフェッチに反映されます (擬似カーソルから TOP 1 を取得する場合と同じです)。

FAST_FORWARD は、最適化された前方専用、読み取り専用のカーソルを作成します。

すべてのカーソルを悪であると判断する前に、利用可能なオプションについて読んでください。

カーソルについては回避策があり、必要になるたびに使用しています。

ID 列を含むテーブル変数を作成します。

作業する必要があるすべてのデータをその中に挿入します。

次に、カウンター変数を使用して while ブロックを作成し、ID 列がカウンターと一致する select ステートメントを使用してテーブル変数から必要なデータを選択します。

このようにすると、何もロックされず、使用するメモリが大幅に減り、安全です。メモリ破損などで何も失われることはありません。

また、ブロックコードは見やすく扱いやすいです。

これは簡単な例です。

DECLARE @TAB TABLE(ID INT IDENTITY, COLUMN1 VARCHAR(10), COLUMN2 VARCHAR(10))

DECLARE @COUNT INT,
        @MAX INT, 
        @CONCAT VARCHAR(MAX), 
        @COLUMN1 VARCHAR(10), 
        @COLUMN2 VARCHAR(10)

SET @COUNT = 1

INSERT INTO @TAB VALUES('TE1S', 'TE21')
INSERT INTO @TAB VALUES('TE1S', 'TE22')
INSERT INTO @TAB VALUES('TE1S', 'TE23')
INSERT INTO @TAB VALUES('TE1S', 'TE24')
INSERT INTO @TAB VALUES('TE1S', 'TE25')

SELECT @MAX = @@IDENTITY

WHILE @COUNT <= @MAX BEGIN
    SELECT @COLUMN1 = COLUMN1, @COLUMN2 = COLUMN2 FROM @TAB WHERE ID = @COUNT

    IF @CONCAT IS NULL BEGIN
        SET @CONCAT = '' 
    END ELSE BEGIN 
        SET @CONCAT = @CONCAT + ',' 
    END

    SET @CONCAT = @CONCAT + @COLUMN1 + @COLUMN2

    SET @COUNT = @COUNT + 1
END

SELECT @CONCAT

カーソルが悪い名前になっているのは、SQL 初心者がカーソルを見つけて、「おい、for ループだ!」と考えるからだと思います。私はそれらの使い方を知っています!」そしてその後、彼らはあらゆるものにそれらを使い続けます。

本来の目的で使用するのであれば、私はそれを責めることはできません。

SQL はセットベースの言語であり、それが最も得意とする言語です。

限られた状況での使用を正当化できるほどカーソルについて十分に理解していない限り、カーソルは依然として悪い選択だと思います。

私がカーソルを好まないもう 1 つの理由は、分かりやすさです。カーソル ブロックは非常に醜いため、明確かつ効果的な方法で使用するのは困難です。

以上のことが述べられましたが、 カーソルが実際に最適な場合もありますが、通常、初心者がカーソルを使用したい場合ではありません。

実行する必要がある処理の性質上、カーソルが必要になる場合がありますが、パフォーマンス上の理由から、可能であればセットベースのロジックを使用して操作を記述する方が常に適切です。

カーソルを使用することを「悪い習慣」とは言いませんが、カーソルは (同等のセットベースのアプローチよりも) サーバー上のリソースをより多く消費しますし、多くの場合、カーソルは必要ありません。それを考慮すると、カーソルに頼る前に他のオプションを検討することをお勧めします。

カーソルにはいくつかの種類があります (前方専用、静的、キーセット、動的)。それぞれに異なるパフォーマンス特性と関連するオーバーヘッドがあります。操作に正しいカーソル タイプを使用していることを確認してください。デフォルトは転送のみです。

カーソルを使用する理由の 1 つは、特に適切な一意キーを持たないデータセットの場合、個々の行を処理して更新する必要がある場合です。その場合、カーソルを宣言するときに FOR UPDATE 句を使用し、UPDATE ... で更新を処理できます。現在の場所。

「サーバー側」カーソルは (ODBC や OLE DB から) 以前はよく使われていましたが、ADO.NET ではサポートされておらず、私の知る限り、今後もサポートされないことに注意してください。

@ Daniel P -> カーソルを使用する必要はありません。集合ベースの理論を使用すると、これを簡単に行うことができます。例えば:SQL 2008 を使用した場合

DECLARE @commandname NVARCHAR(1000) = '';

SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;

EXEC sp_executesql @commandname;

上で言ったことを行うだけです。SQL 2000 でも同じことができますが、クエリの構文は異なります。

ただし、私からのアドバイスは、カーソルをできるだけ避けることです。

ガヤム

カーソルの使用が正当化されるケースは非常にまれです。リレーショナルなセットベースのクエリよりも優れたパフォーマンスを発揮するケースはほとんどありません。プログラマーにとってループを考慮したほうが簡単な場合もありますが、テーブル内の多数の行を更新する場合などにセット ロジックを使用すると、SQL コードの行数が大幅に減るだけでなく、ソリューションが得られます。しかし、その方がはるかに高速に実行されることがよくあります 数桁の大きさ もっと早く。

SQL Server 2005 の早送りカーソルでも、セットベースのクエリには太刀打ちできません。パフォーマンス低下のグラフは、多くの場合、セットベースと比較して n^2 演算のように見え始めます。これは、データ セットが非常に大きくなるにつれて、より線形になる傾向があります。

カーソルには確かにその役割がありますが、それは主に、単一の select ステートメントで結果の集計とフィルタリングを行うのに十分な場合にカーソルがよく使用されるためだと思います。

カーソルを回避すると、SQL Server はクエリのパフォーマンスをより完全に最適化できるようになり、大規模システムでは非常に重要になります。

カーソルは通常、病気ではなく、その症状です。(他の回答で述べたように) セットベースのアプローチは使用しません。

この問題を理解せず、単に「邪悪な」カーソルを回避すれば問題が解決すると信じていると、事態がさら​​に悪化する可能性があります。

たとえば、カーソルの反復を他の反復コード (一時テーブルまたはテーブル変数にデータを移動するなど) に置き換えて、次のような方法で行をループします。

SELECT * FROM @temptable WHERE Id=@counter 

または

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId

このようなアプローチは、別の回答のコードに示されているように、状況をさらに悪化させ、元の問題を解決しません。というアンチパターンです カーゴカルトプログラミング:なぜ何かが悪いのかが分からず、それを避けるためにさらに悪いことを実行してしまうのです。最近、そのようなコード (#temptable を使用し、ID/PK にインデックスを使用しない) をカーソルに戻しました。10000 行をわずかに超える更新に、ほぼ 3 分かかるところ、わずか 1 秒しかかかりませんでした。まだセットベースのアプローチが欠けていますが(よ​​り悪ではないので)、その瞬間に私ができる最善を尽くしました。

この理解の欠如のもう 1 つの症状は、私が時々「単一対象病」と呼ぶものです。データ アクセス レイヤーまたはオブジェクト リレーショナル マッパーを通じて単一のオブジェクトを処理するデータベース アプリケーション。通常は次のようなコードを記述します。

var items = new List<Item>();
foreach(int oneId in itemIds)
{
    items.Add(dataAccess.GetItemById(oneId);
}

の代わりに

var items = dataAccess.GetItemsByIds(itemIds);

1 つ目は通常、大量の SELECT でデータベースをあふれさせ、それぞれ 1 往復します。特にオブジェクト ツリー/グラフが機能し、悪名高い SELECT N+1 問題が発生した場合に起こります。

これはアプリケーション側がリレーショナル データベースとセット ベースのアプローチを理解していないことによるもので、T-SQL や PL/SQL などの手続き型データベース コードを使用する場合のカーソルとまったく同じです。

基本的な問題は、データベースがセットベースの操作、つまりデータ内の関係に基づいて 1 つの素早いステップで大量のデータの選択、更新、削除を行うように設計および調整されていることだと思います。

一方、インメモリ ソフトウェアは個別の操作向けに設計されているため、一連のデータをループし、各アイテムに対して異なる操作を連続的に実行する可能性が最も優れています。

データベースやストレージ アーキテクチャはループを目的として設計されていません。SQL Server 2005 でも、基本的なデータ セットをカスタム プログラムに取り出してメモリ内でループを実行しても、パフォーマンスはそれほど向上しません。 、可能な限り軽量なデータ オブジェクト/構造を使用します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top