簡體   English   中英

在SQL Server中對大量數據執行操作的最佳方法是什么?

[英]What is the best way to perform a manipulation with huge amounts of data in SQL Server?

我們需要在數據庫中執行以下操作:

有一個表A,它的列B_ID是表B的外鍵。表A中有許多行具有相同的B_ID值,我們希望通過克隆B中的相應行並重定向來解決此問題。從A到他們的行。

所有這一切都是相對簡單的,我們已經創建了一個腳本,該腳本通過遍歷游標並調用存儲過程來克隆表B中的行來解決此問題。現在的問題是,A和B表都很大,並且還有一個表A中大量的組指向B中的同一行。

我們最終要做的是(在執行了幾分鍾后)填充了事務日志並崩潰了。 我們甚至嘗試將工作分成合理大小的批處理,然后逐一運行,但這最終也填滿了日志。

除了以某種方式清理日志外,還有什么方法可以處理SQL Server中的批量插入/更新數據,這會更快並且完全不會破壞日志?

如果可以使操作脫機,則可以更改數據庫的恢復模型,進行更改,然后再更改恢復模型。

總體而言,盡管有事務日志可以保護您,允許回滾等,但隨着您出於跟蹤目的而進行刪除等操作,日志會變得更大。

注意:使用此方法時,請確保先進行良好的備份。

我無法想到您為什么要這樣做。 當前的一對多關系有什么問題? 您現在不是要擁有更大的桌子來執行所有工作嗎?

但是,鑒於您要執行此操作,首先要進行事務日志備份的頻率如何? 如果間隔少於每十五分鍾一遍,則進行更改。 備份日志時,日志將被截斷,如果不備份日志,則日志會一直增長,直到空間用盡。 同樣,您為日志指定的增長率可能太小。 增加它,可能也會對您有所幫助。

您可以嘗試在SSIS中進行工作,但我不知道這是否真的可以解決日志記錄問題。 不過,這將有助於提高執行任務的性能。

我不確定這在很多行上如何工作,但是請嘗試一下:

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)

這是批量執行此操作的另一種方法(沒有游標)。 @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:很多,我們想將其設為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

對於需要獲得新顏色的每個標記,結果將有一行。 然后插入新的顏色並捕獲完整的輸出:

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

應該非常有效,因為它只需要查詢生產表一次。 其他所有內容將在臨時表中相對較小的數據上運行。

測試結果:

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空間,則可以用索引的物理登台表替換temp表/表變量。 即使有幾百萬行,也無法填充事務日志並崩潰。

如果您正從多對一(許多A到B)關系變成一對一(一個A到B)關系,那么在我看來,最簡單的方法是在A中創建字段來支持然后對A進行簡單更新,將B中的值復制到其中。

這樣,您就可以完全擺脫B,並且可以在一個更新查詢中執行更改。 就像是:

update tableA SET
  col1 = B.col1,
  col2 = B.col2
from tableA A
inner join tableB on (B.ID = A.B_ID)

這是我的工作:

創建一個查詢,該查詢返回兩個表(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

為避免填充日志,請嘗試在插入之前將數據庫恢復模式設置為簡單:

ALTER DATABASE [database name] SET RECOVERY SIMPLE

然后執行TRUNCATE TABLE C (如果您需要清除舊數據-它不會像deletes一樣添加到日志中)。

然后在cmd窗口中,執行以下命令以將數據批量加載到表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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM