6

移行プロジェクト中に、SQL Server で 400 万件のレコードの更新に直面しました。

アップデートはとても簡単です。ブール値フィールドは true/1 に設定する必要があり、入力はこのフィールドに入力する必要があるすべての ID のリストです (1 行に 1 つの ID)。

このサイズの SQL タスクに関しては、私は正確には専門家ではないので、" WHERE xxx IN ( {list of ids, separated by comma} )" を含む 1 つの UPDATE ステートメントを試してみることから始めました。まず、100 万件のレコードでこれを試しました。テスト サーバー上の小さなデータセットでは、これは魅力的に機能しましたが、運用環境ではエラーが発生しました。そのため、ID のリストの長さを数回短くしましたが、役に立ちませんでした。

次に試したのは、リスト内の各 ID を UPDATE ステートメント (" UPDATE yyy SET booleanfield = 1 WHERE id = '{id}'") に変換することでした。どこかで、x 行ごとに GO を持つのが良いと読んだので、100 行ごとに GO を挿入しました (unix から移植された優れた「sed」ツールを使用)。

そこで、400 万の更新ステートメントのリストをそれぞれ 250.000 の部分に分割し、それらを SQL ファイルとして保存し、最初のステートメントを SQL Server Management Studio (2008) にロードして実行し始めました。私も SQLCMD.exe を試したことに注意してください。しかし、驚いたことに、これは SQL Studio よりも約 10 倍から 20 倍遅く実行されました。

完了するまでに約 1.5 時間かかり、「クエリがエラーで完了しました」という結果になりました。ただし、me​​ssages-list には、「影響を受ける 1 行」と「影響を受ける 0 行」の適切なリストが含まれていました。後者は、id が見つからなかった場合に使用されます。

次に、COUNT(*) を使用してテーブルの更新レコード数を確認したところ、更新ステートメントの数と更新されたレコードの数に数千レコードの違いがあることがわかりました。

その後、レコードが存在しないことが原因ではないかと考えましたが、出力の「0行影響」の量を差し引くと、895レコードの不思議なギャップがありました。

私の質問:

  1. 「クエリがエラーで完了しました」のエラーの説明と原因を見つける方法はありますか。

  2. 895 レコードという謎のギャップはどのように説明できるのでしょうか?

  3. この更新を行うためのより良い、または最良の方法は何ですか? (私がしていることは非常に非効率的であり、エラーが発生しやすい可能性があると考え始めているため)

4

2 に答える 2

6

この質問にアプローチする最良の方法は、400万レコードをテーブルに挿入することです。実際、ビューに「一括挿入」することで、ID列のあるテーブルにそれらを配置できます。

create table TheIds (rownum int identity(1,1), id int);

create view v_TheIds (select id from TheIds);

bulk insert into v_TheIds . . .

データベース内のすべてのデータを使用して、さらに多くのオプションを利用できます。アップデートをお試しください:

update t
    set booleanfield = 1
    where exists (select 1 from TheIds where TheIds.id = t.id)

また、にインデックスを作成する必要がありますTheIds(id)

これは大規模な更新で​​あり、すべて1つのトランザクションとして実行されます。これはパフォーマンスに悪影響を及ぼし、ログを埋め始める可能性があります。rownum次の列を使用して、より小さなトランザクションに分割できます。

update t
    set booleanfield = 1
    where exists (select 1 from TheIds where TheIds.id = t.id and TheIds.rownum < 1000)

ここでのexists句は、と同等のことを行っていleft outer joinます。主な違いは、この相関サブクエリ構文は、更新との結合がデータベース固有である他のデータベースで機能する必要があることです。

このrownum列を使用すると、更新に必要な数の行を選択できます。したがって、全体的な更新が大きすぎる場合は、更新をループに入れることができます。

where rownum < 100000
where rownum between 100000 and 199999
where rownum between 200000 and 299999

等々。これを行う必要はありませんが、何らかの理由で更新をバッチ処理する場合は行うことができます。

重要なアイデアは、IDのリストをデータベース内のテーブルに取り込むことです。これにより、データベースの機能を後続の操作に使用できます。

于 2013-02-09T19:42:34.387 に答える
4

警告: 私はそれをテストすることができませんでした。また、それほど多くのデータを保持できる「プレイグラウンド データベース」も持っていません。

1. と 2. についてはよくわかりませんが、3. については、更新の制限を DB に任せたほうがよいでしょう。

UPDATE TOP(100000) yyy
SET booleanfield = 1
WHERE booleanfield = 0
GO

ドキュメントには、そのTOP制限を持ついくつかのエントリを「ランダムに選択」するように書かれていますが、booleanfield = 0. 更新が報告されなくなるまで、そのクエリを繰り返し実行します。

上記が機能しない場合の別のオプションは、影響を受けるIDをDBから直接選択することです...これは奇妙に見え、私もテストしていませんが、うまくいくことを願っています:

UPDATE yyy
SET booleanfield = 1
FROM (SELECT TOP 100000 id FROM yyy WHERE booleanfield = 0 ORDER BY id ASC) AS xxxx
WHERE yyy.id = xxxx.id;
GO

id(ここでは、テーブルに一意のキーがあると仮定します)。更新が報告されなくなるまで、このクエリを数回 (約 40 回) 実行します。

于 2013-02-09T19:10:00.743 に答える