3

データベースで次の操作を実行する必要があります。

テーブル B への外部キーである列 B_ID を持つテーブル A があります。テーブル A には同じ値の B_ID を持つ多くの行があり、B の対応する行を複製してリダイレクトすることでこれを修正したいと考えています。 Aからそれらまでの行。

これはすべて比較的単純であり、カーソルを反復処理し、テーブル B の行を複製するためのストアド プロシージャを呼び出すことで、これを解決するスクリプトを既に作成しています。問題は、A テーブルと B テーブルの両方が巨大で、 B の同じ行を指しているテーブル A 内の膨大な数のグループ。

最終的に (数分の実行後) トランザクション ログがいっぱいになり、クラッシュします。作業を妥当なサイズのバッチに分割して 1 つずつ実行しようとさえしましたが、これも最終的にログがいっぱいになります。

どういうわけかログをクリーンアップすることとは別に、SQL Serverでデータの一括挿入/更新を処理する方法があり、より高速でログをまったく爆発させませんか?

4

6 に答える 6

2

操作をオフラインにできる場合は、データベースの復旧モデルを変更し、変更を行ってから、復旧モデルを元に戻すことができます。

全体として、トランザクション ログはユーザーを保護し、ロールバックなどを可能にするために存在しますが、追跡目的で削除などを行うとサイズが大きくなります。

注:このアプローチを使用する場合は、最初に非常に適切なバックアップを作成してください....

于 2010-03-05T16:15:22.910 に答える
2

なぜあなたがこれをやりたいのか、私には理解できません。現在の 1 対多の関係の何が問題になっていますか? すべての作業を実行するために、はるかに大きなテーブルが必要になることはありませんか?

ただし、これを行う場合、まずトランザクション ログのバックアップをどのくらいの頻度で取っていますか? 15 分ごとよりも頻度が低い場合は、それを変更します。ログをバックアップすると、ログは切り捨てられます。ログをバックアップしないと、スペースがなくなるまでログが大きくなります。また、ログに指定した成長率が小さすぎる可能性があります。それを増やすと、それもあなたを助けるかもしれません.

SSIS で作業を試すことはできますが、それがログの問題に本当に役立つかどうかはわかりません。ただし、タスクを実行する際のパフォーマンスを向上させるのに役立ちます。

于 2010-03-05T16:28:35.270 に答える
2

これが多くの行でどのように機能するかはわかりませんが、試してみてください:

DECLARE @TableA table (RowID int, B_ID int)
INSERT INTO @TableA VALUES (1,1)
INSERT INTO @TableA VALUES (2,1) --need to copy
INSERT INTO @TableA VALUES (3,2)
INSERT INTO @TableA VALUES (4,2) --need to copy
INSERT INTO @TableA VALUES (5,2) --need to copy
INSERT INTO @TableA VALUES (6,1) --need to copy
INSERT INTO @TableA VALUES (7,3)
INSERT INTO @TableA VALUES (8,3) --need to copy
DECLARE @TableB table (B_ID int, BValues varchar(10))
INSERT INTO @TableB VALUES (1,'one')
INSERT INTO @TableB VALUES (2,'two')
INSERT INTO @TableB VALUES (3,'three')

DECLARE @Max_B_ID int
SELECT @Max_B_ID=MAX(B_ID) FROM @TableB

--if you are using IDENTITY, turn them off here
INSERT INTO @TableB 
        (B_ID, BValues)
        --possibly capture the data to eliminate duplication??
        --OUTPUT INSERTED.tableID, INSERTED.datavalue
        --INTO @y 
    SELECT
        dt.NewRowID, dt.BValues
        FROM (SELECT 
                  RowID, a.B_ID
                      ,@Max_B_ID+ROW_NUMBER() OVER(order by a.B_ID) AS NewRowID,b.BValues
                  FROM (SELECT
                            RowID, B_ID
                            FROM (SELECT 
                                      RowID, a.B_ID, ROW_NUMBER() OVER(PARTITION by a.B_ID order by a.B_ID) AS RowNumber
                                      FROM @TableA a
                                 ) dt
                            WHERE dt.RowNumber>1
                       )a
                      INNER JOIN @TableB  b ON a.B_ID=b.B_ID
             ) dt


UPDATE aa
    SET B_ID=NewRowID
    FROM @TableA   aa
        INNER JOIN (SELECT
                        dt.NewRowID, dt.BValues,dt.RowID
                        FROM (SELECT 
                                  RowID, a.B_ID
                                      ,@Max_B_ID+ROW_NUMBER() OVER(order by a.B_ID) AS NewRowID,b.BValues
                                  FROM (SELECT
                                            RowID, B_ID
                                            FROM (SELECT 
                                                      RowID, a.B_ID, ROW_NUMBER() OVER(PARTITION by a.B_ID order by a.B_ID) AS RowNumber
                                                      FROM @TableA a
                                                 ) dt
                                            WHERE dt.RowNumber>1
                                       )a
                                      INNER JOIN @TableB  b ON a.B_ID=b.B_ID
                             ) dt
                   ) dt2 ON aa.RowID=dt2.RowID

SELECT * FROM @TableA
SELECT * FROM @TableB

出力:

RowID       B_ID
----------- -------
1           1
2           4
3           2
4           6
5           7
6           5
7           3
8           8

(8 row(s) affected)

B_ID        BValues
----------- -------
1           one
2           two
3           three
4           one
5           one
6           two
7           two
8           three

(8 row(s) affected)
于 2010-03-05T16:46:10.727 に答える
2

バッチでこれを行う別の方法を次に示します (カーソルなし)。@KM は動作するはずですが、多くのロックとスキャンが関係しているため、少し遅い/怖いように見えます。ワーキング セットを新しい行のみに制限すると、かなり高速になるはずです。

テスト データのセットアップ スクリプトは次のとおりです。

CREATE TABLE Colors
(
    ColorID int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
    ColorName varchar(50) NOT NULL
)

CREATE TABLE Markers
(
    MarkerID int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
    MarkerName varchar(50) NOT NULL,
    ColorID int NOT NULL,
    CONSTRAINT FK_Markers_Colors FOREIGN KEY (ColorID)
        REFERENCES Colors (ColorID)
)

INSERT Colors (ColorName) VALUES ('Red')
INSERT Colors (ColorName) VALUES ('Green')
INSERT Colors (ColorName) VALUES ('Blue')

INSERT Markers (MarkerName, ColorID) VALUES ('Test1', 1)
INSERT Markers (MarkerName, ColorID) VALUES ('Test2', 1)
INSERT Markers (MarkerName, ColorID) VALUES ('Test3', 1)
INSERT Markers (MarkerName, ColorID) VALUES ('Test4', 2)
INSERT Markers (MarkerName, ColorID) VALUES ('Test5', 2)
INSERT Markers (MarkerName, ColorID) VALUES ('Test6', 3)
INSERT Markers (MarkerName, ColorID) VALUES ('Test7', 3)

1:Many があり、これを 1:1 にします。これを行うには、まず更新のリストをキューに入れます (後でマージを高速化するために、これを他の一意の列のセットにインデックス付けします)。

CREATE TABLE #NewColors
(
    MarkerID int NOT NULL,
    ColorName varchar(50) NOT NULL,
    Seq int NOT NULL,
    CONSTRAINT PK_#NewColors PRIMARY KEY (MarkerID)
)

CREATE INDEX IX_#NewColors
ON #NewColors (ColorName, Seq);

WITH Refs AS
(
    SELECT
        MarkerID,
        ColorID,
    ROW_NUMBER() OVER (PARTITION BY ColorID ORDER BY (SELECT 1)) AS Seq
    FROM Markers
)
INSERT #NewColors (MarkerID, ColorName, Seq)
SELECT r.MarkerID, c.ColorName, r.Seq - 1
FROM Refs r
INNER JOIN Colors c
    ON c.ColorID = r.ColorID
WHERE r.Seq > 1

結果には、新しい色を取得する必要があるマーカーごとに 1 つの行が含まれます。次に、新しい色を挿入し、完全な出力をキャプチャします。

DECLARE @InsertedColors TABLE
(
    ColorID int NOT NULL PRIMARY KEY,
    ColorName varchar(50) NOT NULL
)

INSERT Colors (ColorName)
OUTPUT inserted.ColorID, inserted.ColorName
INTO @InsertedColors
    SELECT ColorName
    FROM #NewColors nc;

そして最後にマージします (ここで、一時テーブルの追加のインデックスが役に立ちます)。

WITH InsertedColorSeq AS
(
    SELECT
        ColorID, ColorName,
        ROW_NUMBER() OVER (PARTITION BY ColorName ORDER BY ColorID) AS Seq
    FROM @InsertedColors
),
Updates AS
(
    SELECT nc.MarkerID, ic.ColorID AS NewColorID
    FROM #NewColors nc
    INNER JOIN InsertedColorSeq ic
    ON ic.ColorName = nc.ColorName
    AND ic.Seq = nc.Seq
)
MERGE Markers m
USING Updates u
    ON m.MarkerID = u.MarkerID
WHEN MATCHED THEN
    UPDATE SET m.ColorID = u.NewColorID;

DROP TABLE #NewColors

これ、実稼働テーブルを 1 回クエリするだけで済むため、非常に効率的です。他のすべては、一時テーブル内の比較的小さなデータで動作します。

結果をテストします。

SELECT m.MarkerID, m.MarkerName, c.ColorID, c.ColorName
FROM Markers m
INNER JOIN Colors c
    ON c.ColorID = m.ColorID

出力は次のとおりです。

MarkerID     MarkerName   ColorID   ColorName
1            Test1        1         Red
2            Test2        6         Red
3            Test3        7         Red
4            Test4        2         Green
5            Test5        5         Green
6            Test6        3         Blue
7            Test7        4         Blue

これはあなたが望むものですよね?カーソルも深刻な醜さもありません。大量のメモリまたは tempdb スペースを消費する場合は、一時テーブル/テーブル変数をインデックス付きの物理ステージング テーブルに置き換えることができます。数百万行であっても、トランザクション ログがいっぱいになってクラッシュすることはありません。

于 2010-03-05T18:03:43.710 に答える
0

多対 1 (多 A 対 1 B) の関係から 1 対 1 (1 A 対 1 B) の関係に移行する場合、これをサポートするために A にフィールドを作成するのが最も簡単な方法だと思います。次に、A で簡単な更新を行い、B から値をコピーします。

このようにして、B を完全に取り除き、1 回の更新クエリで変更を実行できます。何かのようなもの:

update tableA SET
  col1 = B.col1,
  col2 = B.col2
from tableA A
inner join tableB on (B.ID = A.B_ID)
于 2010-03-05T16:14:50.340 に答える
0

これが私がすることです:

2 つのテーブル (A、B) から最終的なテーブル (C) に必要なデータを正確に返すクエリを作成し、それを ExtractData.sql ファイルに入れます。

select
    A.id,
    A.xxx,
    A.yyy,
    B.*
from
   A

   JOIN B
     on B.id = A.id

次に、cmd ウィンドウで次のコマンドを実行して、データをファイルに抽出します。

sqlcmd.exe -S [Server] -U [user] -P [pass] -d [dbname] -i DataExtract.sql -s "|" -h -1 -W -o ExtractData.dat

ログがいっぱいになるのを避けるには、挿入の前に DB リカバリ モードをシンプルに設定してみてください。

ALTER DATABASE [database name] SET RECOVERY SIMPLE

次に、TRUNCATE TABLE C(古いデータを一掃する必要がある場合 - 削除のようにログに追加されません) を実行します。

次に、コマンド ウィンドウで次のコマンドを実行して、データをテーブル C に一括読み込みします。

bcp.exe dbname.dbo.C in ExtractData.dat -S [Server] -U [user] -P [pass] -t "|" -e ExtractData.err -r \n -c

エラー レコードは ExtractData.err ファイルに表示されるため、テーブル C のスキーマを調整する必要がある場合は、抽出されたデータを調整 / 切り捨て / 再読み込みできるため、毎回クエリを実行する必要はありません。

完了したら、リカバリ モードを FULL に戻します。

ALTER DATABASE [database name] SET RECOVERY FULL
于 2010-03-05T16:59:11.490 に答える