10

データベース: SQL Server 2005

問題 : 10 億以上の行を持つ同じテーブル内の 1 つの列から別の列に値をコピーします。

test_table (int id, bigint bigid)

試したこと1:更新クエリ

update test_table set bigid = id 

トランザクション ログがいっぱいになり、トランザクション ログ スペースが不足するためにロールバックします。

2を試しました-次の行の手順

set nocount on
set rowcount = 500000
while @rowcount > 0
begin
 update test_table set bigid = id where bigid is null
 set @rowcount = @@rowcount
 set @rowupdated = @rowsupdated + @rowcount
end
print @rowsupdated

上記の手順は、進行するにつれて速度が低下し始めます。

試した 3 - 更新用のカーソルを作成します。

通常、SQL Server のドキュメントでは推奨されておらず、このアプローチは一度に 1 行ずつ更新されるため、時間がかかりすぎます。

ある列から別の列への値のコピーを高速化できるアプローチはありますか。基本的に私は、更新クエリが一度に 50 万行ずつ順番に 10 億行をリッピングできるようにする「魔法の」キーワードまたはロジックを探しています。

ヒント、ポインタは大歓迎です。

4

7 に答える 7

8

列の人工キーの INT データ型の 21 億の制限に近づいていると思います。はい、それは苦痛です。実際にその制限に達し、修正しようとしている間に生産がシャットダウンされた後よりも、実際に修正する方がはるかに簡単です:)

とにかく、ここにあるアイデアのいくつかは機能します。ただし、速度、効率、インデックス、およびログ サイズについて話しましょう。

ログの増加

一度に 2b 行すべてをコミットしようとしていたため、ログは最初に爆発しました。「チャンクアップ」に関する他の投稿の提案は機能します、ログの問題が完全に解決されない場合があります。

データベースが SIMPLE モードの場合は問題ありません (ログはバッチごとに再利用されます)。データベースが FULL または BULK_LOGGED 復旧モードの場合、SQL がログ領域を再利用できるように、操作の実行中にログ バックアップを頻繁に実行する必要があります。これは、この間にバックアップの頻度を増やすか、実行中にログの使用状況を監視することを意味する場合があります。

インデックスと速度

新しいBIGIDフィールドには(おそらく)インデックスがないため、テーブルにデータが入力されると、すべてのwhere bigid is null回答が遅くなります。(もちろん) BIGID にインデックスを追加することもできますが、それが正しい答えであるとは確信していません。

キー (しゃれた意図) は、元の ID フィールドがおそらく主キー、クラスター化インデックス、またはその両方であるという私の仮定です。その場合、その事実を利用して、ジェスのアイデアのバリエーションを実行しましょう。

set @counter = 1
while @counter < 2000000000 --or whatever
begin
  update test_table set bigid = id 
  where id between @counter and (@counter + 499999) --BETWEEN is inclusive
  set @counter = @counter + 500000
end

ID に既存のインデックスがあるため、これは非常に高速です。

とにかく、ISNULL チェックは実際には必要ありませんでした。インターバルの (-1) も必要ありません。呼び出しの間にいくつかの行を複製しても、それは大したことではありません。

于 2010-09-22T19:47:32.290 に答える
5

UPDATE ステートメントで TOP を使用します。

UPDATE TOP (@row_limit) dbo.test_table
   SET bigid = id 
 WHERE bigid IS NULL
于 2010-09-22T19:07:43.200 に答える
2

これは1回限りですか?もしそうなら、範囲でそれをしてください:

set counter = 500000
while @counter < 2000000000 --or whatever your max id
begin
 update test_table set bigid = id where id between (@counter - 500000) and @counter and bigid is null
 set counter = @counter + 500000
end
于 2010-09-22T18:57:04.120 に答える
2

次のようなものを使用してSET ROWCOUNT、バッチ更新を実行できます。

SET ROWCOUNT 5000;

UPDATE dbo.test_table 
SET bigid = id 
WHERE bigid IS NULL
GO

これを必要なだけ繰り返します。

このようにして、カーソルと while ループの RBAR (row-by-agonizing-row) 現象を回避しながら、トランザクション ログを不必要にいっぱいにすることはありません。

もちろん、実行の合間には、(特にログの) バックアップを作成して、そのサイズを妥当な制限内に保つ必要があります。

于 2010-09-22T18:54:17.223 に答える
0

試しにこれを実行したわけではありませんが、一度に 500k を更新できる場合は、正しい方向に進んでいると思います。

set rowcount 500000
update test_table tt1
set bigid = (SELECT tt2.id FROM test_table tt2 WHERE tt1.id = tt2.id)
where bigid IS NULL

トランザクションをログに記録しないように、復旧モデルを変更することもできます

ALTER DATABASE db1
SET RECOVERY SIMPLE
GO

update test_table
set bigid = id
GO

ALTER DATABASE db1
SET RECOVERY FULL
GO
于 2010-09-22T18:55:10.157 に答える
0

最初のステップがある場合は、操作の前にインデックスを削除することです。これがおそらく、時間の経過とともに速度が低下する原因です。

もう 1 つのオプションは、少し常識にとらわれない考え方です... select で列の値を具体化できるような方法で更新を表現できますか? これができれば、最小ログ操作である SELECT INTO を使用して NEW テーブルを作成できます (2005 年に SIMPLE または BULK LOGGED の復旧モデルに設定されていると仮定します)。これは非常に高速で、古いテーブルを削除し、このテーブルの名前を古いテーブル名に変更して、インデックスを再作成できます。

select id, CAST(id as bigint) bigid into test_table_temp from test_table
drop table test_table
exec sp_rename 'test_table_temp', 'test_table'
于 2010-09-22T19:05:01.067 に答える
0

UPDATE TOP(X) ステートメントを 2 番目に

また、ループしている場合は、間に WAITFOR 遅延または COMMIT を追加して、必要に応じて他のプロセスがテーブルを使用できるようにすることと、すべての更新が完了するまで永久にブロックすることを提案します。

于 2010-09-22T19:23:41.403 に答える