35

TSQL や PLSQL などでデータベース クエリを作成する場合、多くの場合、カーソルを使用して行を反復処理してタスクを実行するか、同じジョブを一度に実行する単一の SQL ステートメントを作成するかを選択できます。

また、単純に大量のデータ セットをアプリケーションに戻してから、C#、Java、PHP などを使用して行ごとに処理するという選択肢もあります。

セットベースのクエリを使用する方が良いのはなぜですか? この選択の背後にある理論は何ですか? カーソルベースのソリューションとそれに相当するリレーショナル ソリューションの良い例は?

4

11 に答える 11

19

私が認識している主な理由は、セットベースの操作を複数のスレッドで実行することにより、エンジンで最適化できることです。たとえば、クイックソートを考えてみてください。ソートしているリストを複数の「チャンク」に分割し、それぞれを独自のスレッドで個別にソートできます。SQL エンジンは、1 つのセットベースのクエリで大量のデータに対して同様のことを行うことができます。

カーソルベースの操作を実行する場合、エンジンはシーケンシャルにしか実行できず、操作はシングル スレッドである必要があります。

于 2008-08-23T12:08:33.480 に答える
16

上記の「DBMSに作業を任せる」(これは優れたソリューションです)に加えて、DBMSにクエリを残す理由は他にもいくつかあります。

  • (主観的に)読みやすくなっています。後でコードを見るときは、ループなどを含む複雑なストアドプロシージャ(またはクライアント側のコード)を解析しようとしますか、それとも簡潔なSQLステートメントを調べますか?
  • ネットワークの往復を回避します。なぜそのすべてのデータをクライアントにプッシュしてから、さらにプッシュするのですか?必要がないのに、なぜネットワークを破壊するのですか?
  • それは無駄です。DBMSとアプリサーバーは、データを処理するためにそのデータの一部またはすべてをバッファリングする必要があります。無限のメモリがない場合は、他のデータをページアウトする可能性があります。なぜメモリからおそらく重要なものを追い出し、ほとんど役に立たない結果セットをバッファリングするのですか?
  • どうしてそうしませんか?信頼性が高く、非常に高速なDBMSを購入した(または使用している)。使ってみませんか?
于 2008-08-23T13:42:12.210 に答える
16

セットベースのクエリは (通常) 高速です。理由は次のとおりです。

  1. クエリオプティマイザーが最適化するためのより多くの情報があります
  2. ディスクからのバッチ読み取りが可能
  3. ロールバック、トランザクション ログなどに関連するログが少なくなります。
  4. 取得されるロックが少なくなり、オーバーヘッドが減少します
  5. RDBMS はセット ベースのロジックに重点を置いているため、RDBMS はそのために大幅に最適化されています (多くの場合、手続き型のパフォーマンスが犠牲になります)。

ただし、データを中間層に引き出して処理することは、DB サーバーの処理オーバーヘッドを取り除くため、便利な場合があります (これはスケーリングが最も困難であり、通常は他のことも行っています)。また、通常、中間層には同じオーバーヘッド (または利点) はありません。トランザクション ログ、組み込みのロックとブロックなど - 必要で便利な場合もあれば、単にリソースを浪費する場合もあります。

電話交換局に基づいて市外局番を割り当てる手続き型ロジックとセット ベースの例 (T-SQL) を使用した単純なカーソル:

--Cursor
DECLARE @phoneNumber char(7)
DECLARE c CURSOR LOCAL FAST_FORWARD FOR
   SELECT PhoneNumber FROM Customer WHERE AreaCode IS NULL
OPEN c
FETCH NEXT FROM c INTO @phoneNumber
WHILE @@FETCH_STATUS = 0 BEGIN
   DECLARE @exchange char(3), @areaCode char(3)
   SELECT @exchange = LEFT(@phoneNumber, 3)

   SELECT @areaCode = AreaCode 
   FROM AreaCode_Exchange 
   WHERE Exchange = @exchange

   IF @areaCode IS NOT NULL BEGIN
       UPDATE Customer SET AreaCode = @areaCode
       WHERE CURRENT OF c
   END
   FETCH NEXT FROM c INTO @phoneNumber
END
CLOSE c
DEALLOCATE c
END

--Set
UPDATE Customer SET
    AreaCode = AreaCode_Exchange.AreaCode
FROM Customer
JOIN AreaCode_Exchange ON
    LEFT(Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange
WHERE
    Customer.AreaCode IS NULL
于 2008-08-23T13:06:58.753 に答える
9

実際の例が必要でした。私の会社には、30,000 件のレコードを処理するのに 40 分以上かかるカーソルがありました (200,000 件を超えるレコードを更新する必要がある場合もありました)。カーソルなしで同じタスクを実行するには、45 秒かかりました。別のケースでは、カーソルを削除したところ、処理時間が 24 時間以上から 1 分未満になりました。1 つは select の代わりに values 句を使用した挿入で、もう 1 つは結合の代わりに変数を使用した更新でした。経験則として、挿入、更新、または削除の場合は、タスクを実行するためのセットベースの方法を探す必要があります。

カーソルには用途があります (または、コードはそもそもカーソルではないでしょう) が、リレーショナル データベースにクエリを実行する場合は非常にまれです (それらを使用するように最適化されている Oracle を除く)。前のレコードの値に基づいて計算を行う場合 (累計) は、より高速になる可能性があります。しかし、それでもテストする必要があります。

カーソルを使用するもう 1 つの限定的なケースは、バッチ処理を行うことです。セットベースの方法で一度に多くのことをしようとすると、他のユーザーに対してテーブルがロックされる可能性があります。非常に大きなセットがある場合は、ロックをあまり長く保持しない小さなセットベースの挿入、更新、または削除に分割し、カーソルを使用してセットを実行することをお勧めします。

カーソルの 3 つ目の用途は、入力値のグループを介してシステム ストアド プロシージャを実行することです。これは一般的に小さなセットに限定されており、誰もシステム プロシージャをいじってはならないため、これは管理者が行うのに許容されることです。大きなバッチを処理してコードを再利用するために、ユーザーが作成したストアド プロシージャで同じことを行うことはお勧めしません。ほとんどの場合、コードの再利用よりもパフォーマンスの方が優先されるため、パフォーマンスが向上するセットベースのバージョンを作成することをお勧めします。

于 2008-12-01T17:44:05.927 に答える
3

本当の答えは、プログラミングのすべてのアプローチと同様に、どちらが優れているかによって決まると思います。一般に、セットベースの言語は、それが実行するように設計されているため、より効率的になります。カーソルが有利になる場所は2つあります。

  1. 行のロックが受け入れられないデータベース内の大規模なデータセットを更新しています(おそらく本番時間中)。セットベースの更新では、テーブルが数秒(または数分)ロックされる可能性がありますが、カーソル(正しく書き込まれている場合)はロックされません。カーソルは、一度に1つずつ更新される行を蛇行する可能性があり、他に影響を与えることを心配する必要はありません。

  2. SQLを使用する利点は、最適化のための作業の大部分がほとんどの状況でデータベースエンジンによって処理されることです。エンタープライズクラスのdbエンジンを使用することで、設計者は、システムがデータを効率的に処理できるようにするために、骨の折れる努力を重ねてきました。欠点は、SQLがセットベースの言語であるということです。それを使用するには、データのセットを定義できる必要があります。これは簡単に聞こえますが、状況によってはそうではありません。クエリは非常に複雑なため、エンジンの内部オプティマイザが実行パスを効果的に作成できず、何が起こるかを推測できません... 32プロセッサを搭載した非常に強力なボックスは、知らないため、単一のスレッドを使用してクエリを実行します他のことをする方法、そのため、複数のアプリケーションサーバーではなく1つしかないデータベースサーバーでプロセッサ時間を浪費します(理由1に戻ると、データベースサーバーで実行する必要のある他のものとのリソース競合が発生します)。行ベースの言語(C#、PHP、JAVAなど)を使用すると、何が起こるかをより細かく制御できます。データセットを取得して、希望どおりに実行するように強制できます。(複数のスレッドなどで実行するようにデータセットを分離します)。ほとんどの場合、行を更新するためにエンジンにアクセスする必要があるため、データベースエンジンで実行するほど効率的ではありませんが、行を更新するために1000回以上の計算を行う必要がある場合(そして、100万行あるとしましょう)、データベースサーバーで問題が発生し始める可能性があります。データベースサーバーで実行する必要がある他のものとのリソース競合に遭遇します)。行ベースの言語(C#、PHP、JAVAなど)を使用すると、何が起こるかをより細かく制御できます。データセットを取得して、希望どおりに実行するように強制できます。(複数のスレッドなどで実行するようにデータセットを分離します)。ほとんどの場合、行を更新するためにエンジンにアクセスする必要があるため、データベースエンジンで実行するほど効率的ではありませんが、行を更新するために1000回以上の計算を行う必要がある場合(そして、100万行あるとしましょう)、データベースサーバーで問題が発生し始める可能性があります。データベースサーバーで実行する必要がある他のものとのリソース競合に遭遇します)。行ベースの言語(C#、PHP、JAVAなど)を使用すると、何が起こるかをより細かく制御できます。データセットを取得して、希望どおりに実行するように強制できます。(複数のスレッドなどで実行するようにデータセットを分離します)。ほとんどの場合、行を更新するためにエンジンにアクセスする必要があるため、データベースエンジンで実行するほど効率的ではありませんが、行を更新するために1000回以上の計算を行う必要がある場合(そして、100万行あるとしましょう)、データベースサーバーで問題が発生し始める可能性があります。(複数のスレッドなどで実行するようにデータセットを分離します)。ほとんどの場合、行を更新するためにエンジンにアクセスする必要があるため、データベースエンジンで実行するほど効率的ではありませんが、行を更新するために1000回以上の計算を行う必要がある場合(そして、100万行あるとしましょう)、データベースサーバーで問題が発生し始める可能性があります。(複数のスレッドなどで実行するようにデータセットを分離します)。ほとんどの場合、行を更新するためにエンジンにアクセスする必要があるため、データベースエンジンで実行するほど効率的ではありませんが、行を更新するために1000回以上の計算を行う必要がある場合(そして、100万行あるとしましょう)、データベースサーバーで問題が発生し始める可能性があります。

于 2008-08-23T13:29:56.983 に答える
1

データベースを使用することは、使用するように設計されていることに帰着すると思います。リレーショナル データベース サーバーは、設定されたロジックで表現された質問に最適に応答するように特別に開発および最適化されています。

機能的には、カーソルのペナルティは製品によって大きく異なります。一部の (ほとんどの?) rdbms は、少なくとも部分的に isam エンジンの上に構築されています。質問が適切で、ベニアが十分に薄い場合は、実際にはカーソルを使用するのが効率的かもしれません。しかし、これは、自分の dbms のブランドに関して、試す前によく知っておくべきことの 1 つです。

于 2008-12-01T18:04:36.870 に答える
1

前述のように、データベースはセット操作用に最適化されています。文字通り、エンジニアは座ってそのデータベースを長時間デバッグ/チューニングしました。それらを最適化する可能性はかなり低いです。ディスクの読み取り/書き込みのバッチ処理、キャッシング、マルチスレッドなど、操作するデータのセットがある場合は、さまざまな楽しいトリックを試すことができます。また、一部の操作はオーバーヘッド コストが高くなりますが、一度に多数のデータに対して実行すると、データ 1 個あたりのコストは低くなります。一度に 1 行しか作業していない場合、これらのメソッドや操作の多くは実行できません。

たとえば、データベースの結合方法を見てください。Explain Plan を見ると、結合を行ういくつかの方法がわかります。ほとんどの場合、カーソルを使用して、あるテーブルで行ごとに移動し、別のテーブルから必要な値を選択します。基本的に、これは入れ子になったループのようなものですが、ループのタイトさはありません (機械語にコンパイルされ、非常に最適化されている可能性が最も高い)。SQL Server 自体には、多数の参加方法があります。行がソートされている場合、何らかのタイプのマージ アルゴリズムが使用されます。1 つのテーブルが小さい場合は、1 つのテーブルがハッシュ ルックアップ テーブルに変換され、1 つのテーブルからルックアップ テーブルへの O(1) ルックアップを実行することによって結合が行われます。多くの DBMS には、カーソル内の 1 つのテーブルから値を検索するのに勝る多数の結合戦略があります。

ハッシュ ルックアップ テーブルを作成する例を見てください。長さ n と長さ m の 2 つのテーブルを結合する場合、テーブルを構築するにはおそらく m 回の操作が必要です。m は小さい方のテーブルです。各ルックアップは一定時間である必要があるため、n 回の操作になります。したがって、基本的にハッシュ結合の効率は m (セットアップ) + n (ルックアップ) 程度です。自分でそれを行い、ルックアップ/インデックスがないと仮定すると、n 行のそれぞれについて、m レコードを検索する必要があります (平均で m/2 検索に相当します)。したがって、基本的に操作のレベルは m + n (多数のレコードを一度に結合する) から m * n / 2 (カーソルを介してルックアップを行う) になります。また、操作も簡素化されています。カーソルのタイプによっては、カーソルの各行をフェッチすることは、最初のテーブルから別の選択を行うことと同じ場合があります。

ロックもあなたを殺します。テーブルにカーソルがある場合、行をロックしています (SQL サーバーでは、これは static および forward_only カーソルではそれほど深刻ではありません...しかし、私が目にするカーソル コードの大部分は、これらのオプションを指定せずにカーソルを開くだけです)。セットで操作を行う場合、行は引き続きロックされますが、時間は短くなります。また、オプティマイザーはユーザーが行っていることを確認でき、一連の行やページではなく、テーブル全体をロックする方が効率的であると判断する場合があります。しかし、行ごとに行けば、オプティマイザはわかりません。

もう1つのことは、Oracleの場合、カーソル操作を実行するために非常に最適化されているため、Oracleのセットベースの操作とカーソルのペナルティは、SQL Serverの場合と同じではないということです。私は Oracle の専門家ではないので、はっきりとは言えません。しかし、複数の Oracle 担当者から、Oracle ではカーソルの方がはるかに効率的であると言われました。したがって、オラクルのために長男を犠牲にした場合、カーソルについて心配する必要はないかもしれません。地元の高給のオラクルDBAに相談してください:)

于 2008-12-01T18:20:29.380 に答える
0

クエリで作業を行うことを好む背景にある考え方は、データベース エンジンがクエリを再構築することで最適化できるということです。これが、データベースが実際に何をしているかを確認するために、クエリで EXPLAIN を実行する理由でもあります。(たとえば、インデックス、テーブル サイズ、場合によっては列内の値の分布に関する知識さえも活用します。)

とはいえ、実際の具体的なケースで優れたパフォーマンスを得るには、ルールを曲げたり破ったりする必要がある場合があります。

ああ、別の理由は制約である可能性があります。すべての更新後に制約がチェックされる場合、一意の列を 1 ずつインクリメントしても問題ないかもしれませんが、1 つずつ実行すると衝突が発生します。

于 2008-08-23T12:16:59.967 に答える
0

セットベースは、カーソルの行セットと同じ数の操作で 1 回の操作で実行されます。

于 2008-08-23T12:41:07.210 に答える
0

本当の答えは、EF Coddの本を手に入れて、リレーショナル代数をブラッシュアップすることです。次に、Big O 記法に関する優れた本を入手してください。20 年近く IT に携わってきた今、これは現代の MIS または CS 学位の大きな悲劇の 1 つです。実際に計算を研究している人はほとんどいません。「コンピューター」の「計算」の部分を知っていますか?構造化照会言語 (およびそのすべてのスーパーセット) は、リレーショナル代数の実用的なアプリケーションにすぎません。はい、RDBMS はメモリ管理と読み取り/書き込みを最適化していますが、手続き型言語についても同じことが言えます。私がそれを読んだとき、元の質問はIDEやソフトウェアに関するものではなく、ある計算方法と別の計算方法の効率に関するものです。

Big O 表記法に少し慣れただけでも、一連のデータを処理する場合、反復が宣言ステートメントよりもコストがかかる理由が明らかになります。

于 2009-05-13T20:05:57.120 に答える