SQL 7 の時代にパフォーマンス上の理由があることは知っていましたが、SQL Server 2005 にも同じ問題が存在するのでしょうか? ストアド プロシージャに個別に処理したい結果セットがある場合でも、カーソルは不適切な選択ですか? もしそうなら、なぜですか?
11 に答える
カーソルがメモリを占有し、ロックを作成するためです。
あなたが実際に行っているのは、セットベースのテクノロジーを非セットベースの機能に強制しようとしていることです。そして、公平を期すために、カーソルには用途があることを指摘しておく必要がありますが、セットベースのソリューションを使用することに慣れていない多くの人々は、セットベースのソリューションを理解する代わりにカーソルを使用しているため、眉をひそめています。
ただし、カーソルを開くと、基本的にそれらの行をメモリにロードしてロックし、潜在的なブロックを作成します。次に、カーソルを巡回しながら、他のテーブルに変更を加えながら、カーソルのすべてのメモリとロックを開いたままにします。
これらはすべて、他のユーザーにパフォーマンスの問題を引き起こす可能性があります。
したがって、原則として、カーソルは嫌われます。特に、それが問題を解決するために到達した最初の解決策である場合.
The above comments about SQL being a set-based environment are all true. However there are times when row-by-row operations are useful. Consider a combination of metadata and dynamic-sql.
As a very simple example, say I have 100+ records in a table that define the names of tables that I want to copy/truncate/whatever. Which is best? Hardcoding the SQL to do what I need to? Or iterate through this resultset and use dynamic-SQL (sp_executesql) to perform the operations?
There is no way to achieve the above objective using set-based SQL.
So, to use cursors or a while loop (pseudo-cursors)?
SQL Cursors are fine as long as you use the correct options:
INSENSITIVE will make a temporary copy of your result set (saving you from having to do this yourself for your pseudo-cursor).
READ_ONLY will make sure no locks are held on the underlying result set. Changes in the underlying result set will be reflected in subsequent fetches (same as if getting TOP 1 from your pseudo-cursor).
FAST_FORWARD will create an optimised forward-only, read-only cursor.
Read about the available options before ruling all cursors as evil.
カーソルが必要になるたびに使用するカーソルに関する回避策があります。
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 ... WHERE CURRENT OF で更新を処理できます。
「サーバー側」カーソルは (ODBC および OLE DB から) 以前は一般的でしたが、ADO.NET はそれらをサポートしていません。
カーソルの使用が正当化されるケースはほとんどありません。リレーショナルなセットベースのクエリよりもパフォーマンスが優れているケースはほとんどありません。プログラマーがループの観点から考える方が簡単な場合もありますが、セット ロジックを使用すると、たとえばテーブル内の多数の行を更新すると、SQL コードの行数が大幅に減るだけでなく、解決策が得られます。しかし、それははるかに速く、多くの場合数桁速く実行されます。
Sql Server 2005 の早送りカーソルでさえ、セット ベースのクエリには太刀打ちできません。パフォーマンス低下のグラフは、多くの場合、セットベースと比較して n^2 操作のように見え始めます。これは、データ セットが非常に大きくなるにつれて、より線形になる傾向があります。
Cursors are usually not the disease, but a symptom of it: not using the set-based approach (as mentioned in the other answers).
Not understanding this problem, and simply believing that avoiding the "evil" cursor will solve it, can make things worse.
For example, replacing cursor iteration by other iterative code, such as moving data to temporary tables or table variables, to loop over the rows in a way like:
SELECT * FROM @temptable WHERE Id=@counter
or
SELECT TOP 1 * FROM @temptable WHERE Id>@lastId
Such an approach, as shown in the code of another answer, makes things much worse and doesn't fix the original problem. It's an anti-pattern called cargo cult programming: not knowing WHY something is bad and thus implementing something worse to avoid it! I recently changed such code (using a #temptable and no index on identity/PK) back to a cursor, and updating slightly more than 10000 rows took only 1 second instead of almost 3 minutes. Still lacking set-based approach (being the lesser evil), but the best I could do that moment.
Another symptom of this lack of understanding can be what I sometimes call "one object disease": database applications which handle single objects through data access layers or object-relational mappers. Typically code like:
var items = new List<Item>();
foreach(int oneId in itemIds)
{
items.Add(dataAccess.GetItemById(oneId);
}
instead of
var items = dataAccess.GetItemsByIds(itemIds);
The first will usually flood the database with tons of SELECTs, one round trip for each, especially when object trees/graphs come into play and the infamous SELECT N+1 problem strikes.
This is the application side of not understanding relational databases and set based approach, just the same way cursors are when using procedural database code, like T-SQL or PL/SQL!
@ Daniel P -> カーソルを使用する必要はありません。セットベースの理論を使用して簡単にそれを行うことができます。例: Sql 2008 で
DECLARE @commandname NVARCHAR(1000) = '';
SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;
EXEC sp_executesql @commandname;
あなたが上で言ったことをするだけです。Sql 2000 でも同じことができますが、クエリの構文が異なります。
ただし、私のアドバイスは、カーソルをできるだけ避けることです。
ガヤム
カーソルにはその役割がありますが、主な理由は、単一の選択ステートメントで結果の集計とフィルタリングを提供するのに十分な場合に使用されることが多いためだと思います。
カーソルを回避すると、SQL Server はクエリのパフォーマンスをより完全に最適化できます。これは、大規模なシステムでは非常に重要です。
基本的な問題は、データベースがセットベースの操作用に設計および調整されていることだと思います。つまり、データ内の関係に基づいて、大量のデータを 1 つのクイック ステップで選択、更新、および削除します。
一方、インメモリ ソフトウェアは個々の操作用に設計されているため、一連のデータをループし、各アイテムに対して異なる操作を連続して実行する可能性があることが、最も得意とすることです。
ループは、データベースまたはストレージ アーキテクチャが設計されたものではありません。また、SQL Server 2005 でさえ、基本的なデータ セットをカスタム プログラムに引き出してメモリ内でループを実行した場合、得られるパフォーマンスは得られません。 、可能な限り軽量なデータ オブジェクト/構造を使用します。