Cập nhật bảng có hơn 850 triệu hàng dữ liệu


7

Tôi đã được giao nhiệm vụ viết một truy vấn cập nhật để cập nhật một bảng có hơn 850 triệu hàng dữ liệu. Dưới đây là các cấu trúc bảng:

Bảng nguồn:

    CREATE TABLE [dbo].[SourceTable1](
    [ProdClassID] [varchar](10) NOT NULL,
    [PriceListDate] [varchar](8) NOT NULL,
    [PriceListVersion] [smallint] NOT NULL,
    [MarketID] [varchar](10) NOT NULL,
    [ModelID] [varchar](20) NOT NULL,
    [VariantId] [varchar](20) NOT NULL,
    [VariantType] [tinyint] NULL,
    [Visibility] [tinyint] NULL,
 CONSTRAINT [PK_SourceTable1] PRIMARY KEY CLUSTERED 
(
    [VariantId] ASC,
    [ModelID] ASC,
    [MarketID] ASC,
    [ProdClassID] ASC,
    [PriceListDate] ASC,
    [PriceListVersion] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, 
ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90)
    )

CREATE TABLE [dbo].[SourceTable2](
    [Id] [uniqueidentifier] NOT NULL,
    [ProdClassID] [varchar](10) NULL,
    [PriceListDate] [varchar](8) NULL,
    [PriceListVersion] [smallint] NULL,
    [MarketID] [varchar](10) NULL,
    [ModelID] [varchar](20) NULL,
 CONSTRAINT [PK_SourceTable2] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, 
ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 91) ON [PRIMARY]
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

SourceTable1chứa 52 triệu hàng dữ liệu và SourceTable2chứa 400.000 hàng dữ liệu.

Đây là TargetTablecấu trúc

CREATE TABLE [dbo].[TargetTable](
    [ChassisSpecificationId] [uniqueidentifier] NOT NULL,
    [VariantId] [varchar](20) NOT NULL,
    [VariantType] [tinyint] NULL,
    [Visibility] [tinyint] NULL,
 CONSTRAINT [PK_TargetTable] PRIMARY KEY CLUSTERED 
(
    [ChassisSpecificationId] ASC,
    [VariantId] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 71) ON [PRIMARY]
    ) ON [PRIMARY]

Mối quan hệ giữa các bảng này như sau:

  • SourceTable1.VariantID có liên quan đến TargetTable.VariantID
  • SourceTable2.ID có liên quan đến TargetTable.ChassisSpecificationId

Yêu cầu cập nhật như sau:

  1. Lấy các giá trị cho VariantTypeVisibilitytừ SourceTable1cho mỗi VariantID, có giá trị tối đa trong PriceListVersioncột.
  2. Lấy giá trị của IDcột từ SourceTable2nơi các giá trị của ModelID, ProdClassID, PriceListDateMarketIDtrận đấu với cùng kỳ SourceTable1.
  3. Bây giờ hãy cập nhật TargetTablevới các giá trị cho VariantTypeVisibilitynơi ChassisspecificationIDkhớp SourceTable2.IDVariantIDkhớpSourceTable1.VariantID

Thách thức là thực hiện cập nhật này trên sản xuất trực tiếp, với khóa tối thiểu. Đây là truy vấn tôi đã đặt cùng nhau.

-- Check if Temp table already exists and drop if it does
IF EXISTS(
        SELECT NULL 
        FROM tempdb.sys.tables
        WHERE name LIKE '#CSpec%'
      )
BEGIN
    DROP TABLE #CSpec;
END;

-- Create Temp table to assign sequence numbers
CREATE Table #CSpec
(
    RowID int,
    ID uniqueidentifier,
    PriceListDate VarChar(8),
    ProdClassID VarChar(10),
    ModelID VarChar(20),
    MarketID Varchar(10)
 );

-- Populate temp table 
INSERT INTO #CSpec
SELECT ROW_NUMBER() OVER (ORDER BY MarketID) RowID,
       CS.id, 
       CS.pricelistdate, 
       CS.prodclassid, 
       CS.modelid, 
       CS.marketid 
FROM   dbo.SourceTable2 CS 
WHERE CS.MarketID IS NOT NULL;

-- Declare variables to hold values used for updates
DECLARE @min            int, 
        @max            int,
        @ID             uniqueidentifier,
        @PriceListDate  varchar(8),
        @ProdClassID    varchar(10),
        @ModelID        varchar(20),
        @MarketID       varchar(10);
-- Set minimum and maximum values for looping
SET @min = 1;
SET @max = (SELECT MAX(RowID) From #CSpec);

-- Populate other variables in a loop
WHILE @min <= @max
BEGIN
    SELECT 
        @ID = ID,
        @PriceListDate = PriceListDate,
        @ProdClassID = ProdClassID,
        @ModelID = ModelID,
        @MarketID = MarketID
    FROM #CSpec
    WHERE RowID = @min;  

-- Use CTE to get relevant values from SourceTable1 
    ;WITH Variant_CTE AS
    (
    SELECT  V.variantid, 
            V.varianttype, 
            V.visibility,
            MAX(V.PriceListVersion) LatestPriceVersion
    FROM    SourceTable1 V 
    WHERE       V.ModelID = @ModelID
            AND V.ProdClassID = @ProdClassID
            AND V.PriceListDate = @PriceListDate
            AND V.MarketID = @MarketID
    GROUP BY
            V.variantid, 
            V.varianttype, 
            V.visibility
    )

-- Update the TargetTable with the values obtained in the CTE
    UPDATE      SV 
        SET     SV.VariantType = VC.VariantType, 
                SV.Visibility = VC.Visibility
    FROM        spec_variant SV 
    INNER JOIN  TargetTable VC
    ON          SV.VariantId = VC.VariantId
    WHERE       SV.ChassisSpecificationId = @ID
                AND SV.VariantType IS NULL
                AND SV.Visibility IS NULL;

    -- Increment the value of loop variable
    SET @min = @min+1;
END
-- Clean up
DROP TABLE #CSpec

Mất khoảng 30 giây khi tôi đặt giới hạn số lần lặp là 10, bằng cách mã hóa giá trị của @maxbiến. Tuy nhiên, khi tôi tăng giới hạn lên 50 lần lặp thì phải mất gần 4 phút để hoàn thành. Tôi lo ngại rằng thời gian thực hiện trong 400.000 lần lặp sẽ chạy trong nhiều ngày khi sản xuất. Tuy nhiên, điều đó vẫn có thể được chấp nhận, nếu điều TargetTableđó không bị khóa, ngăn người dùng truy cập nó.

Tất cả các đầu vào đều được chào đón.

Cảm ơn, Raj


2 người đã bỏ phiếu cho câu hỏi này. Quan tâm giải thích tại sao?
Raj

1
Mọi người đã không bỏ phiếu cho việc đóng cửa nhưng để di chuyển đến trang DBA.SE nơi có nhiều cơ hội tốt hơn bạn sẽ nhận được câu trả lời của chuyên gia.
ypercubeᵀᴹ

2
Một số điều khác cần xem xét: Thống kê và cập nhật hàng loạt. Xem một bài viết thông tin ở đây trên blog MSDN SQL Thay thế .
Mary

1
Cảm ơn rất nhiều cho tất cả các đầu vào kẻ. Tôi đã có thể điều chỉnh tập lệnh thêm một lần nữa, tạo các chỉ mục có liên quan và nhờ liên kết của @ Marian, tôi cũng có thể sử dụng song song nhiều phiên và vừa hoàn thành cập nhật DB thử nghiệm với 175 triệu hàng trong khoảng 40 phút. Đó là một cải tiến lớn từ 9,5 giờ mà nó đã đưa người tiền nhiệm của tôi vào cùng DB thử nghiệm. Nhiều đánh giá cao.
Raj

Câu trả lời:


5

Để tăng tốc mọi thứ, bạn có thể thử

  • Thêm khóa chính vào # CSpec.RowID để bạn không quét nó mỗi lần lặp
  • Thay đổi CTE thành bảng tạm thời với PK phù hợp. Xem điểm tiếp theo quá
  • Thêm một chỉ mục trên SourceTable1 để khớp với mệnh đề CTE WHERE: hiện tại PK sẽ được quét, nghĩa là tất cả các hàng SourceTable1 sẽ được quét mỗi lần lặp. Tất cả 52 triệu hàng
  • SourceTable2.MarketID cũng không có chỉ mục, nhưng tôi sẽ không lo lắng về điều này bởi vì nó chỉ được quét một lần (theo tôi hiểu)

Các kế hoạch truy vấn ở đây sẽ hiển thị rất nhiều lần quét vì bạn có chỉ mục kém cho các hoạt động bạn đang thực hiện.

Lập chỉ mục bảng xuất hiện OK

Một quan sát khác: nhận dạng duy nhất và varchar là những lựa chọn không tốt cho các chỉ mục được nhóm (PK của bạn ở đây): quá rộng, không tăng, ít nhất là so sánh thu thập

Chỉnh sửa, một quan sát khác (nhờ @Marian)

Chỉ số cụm của bạn là rộng nói chung. Mọi chỉ mục không phân cụm đều trỏ đến chỉ mục được phân cụm, điều đó có nghĩa là một chỉ số NC quá lớn

Bạn có thể có thể đạt được kết quả tương tự bằng cách sắp xếp lại PK cụm.


Cảm ơn câu trả lời. Làm cho ý nghĩa thêm PK vào bảng tạm thời #CSpec. Tôi quyết định sử dụng CTE vì mỗi lần lặp chỉ cập nhật trung bình 16 - 20 hàng dữ liệu. Chỉ mục trên SourceTable1 đã được tạo. Đối với các quan sát của bạn để lựa chọn PK, tôi đồng ý. Đáng buồn thay, điều này được kế thừa và những thay đổi đối với lược đồ DB có thể không thực hiện được. Cảm ơn.
Raj

4

Đăng SQL cuối cùng cho quá trình này, vì lợi ích của cộng đồng

/********************************************************************************************************************
*  Notes: Since this approach executes in a loop inside an explicit transaction, locks will be obtained and         *
*  released for each iteration, thus minimizing impact on other users accessing the same table at the same time.    *
*                                                                                                                   *
*  This process would update 10,000 to 12,000 rows per second, and thus is estimated to run for approximately       *
*  23 hours on production with 850 million rows in Spec_Variant table. However, we can harness the power of         *
*  mutli-threading, by statically defining the @min and @max variable values and then running multiple sessions     *
*  of this update. This will reduce the time required to 23 hours divided by the number of sessions. In other words,* 
*  if we run 8 sessions of this update query parallelly, it should complete in 23/8 ~ 3 hours. If multiple sessions *
*  are possible, then the temp table needs to be created as a global temp table and populated in its own session.   *
*  Additionally, each sessions @max and @min values need to be hard coded,for example, 1-50000, 50001-100000, etc.  *
*********************************************************************************************************************/

-- However, to make this possible, we will have to use...

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

-- ... this would be the ideal setting to minimize locking. Before using this, we will need to execute
-- ALTER DATABASE MyDatabase
-- SET ALLOW_SNAPSHOT_ISOLATION ON

-- Alternately, if access rights permit, executing 
-- DBCC TRACEON(1211,-1) will disable lock escalation. Else, the TRANSACTION ISOLATION LEVEL can be left at 
-- default (READ COMMITTED), but will not allow us to run multiple sessions.

SET NOCOUNT ON;

-- Check if Temp table already exists and drop if it does
IF EXISTS(
        SELECT NULL 
        FROM tempdb.sys.tables
        WHERE name LIKE '#CSpec%'
      )
BEGIN
    DROP TABLE #CSpec;
END;

-- Create Temp table to assign sequence numbers
CREATE Table #CSpec
    (
    RowID           int PRIMARY KEY,
    ID              uniqueidentifier,
    PriceListDate   VarChar(8),
    ProdClassID     VarChar(10),
    ModelID         VarChar(20),
    MarketID        Varchar(10)
    );

-- Populate temp table 
INSERT INTO #CSpec
SELECT ROW_NUMBER() OVER (ORDER BY MarketID) RowID,
       CS.id, 
       CS.pricelistdate, 
       CS.prodclassid, 
       CS.modelid, 
       CS.marketid 
FROM   dbo.SourceTable2 CS 
WHERE CS.MarketID IS NOT NULL
-- This AND clause will allow this process to be run multiple times in timed sessions and will prevent
-- an attempt to update rows that were already updated in an earlier session. If the process will be run 
-- only once from start to finish, this block can be commented out
AND CS.Id NOT IN 
            (
                SELECT DISTINCT ChassisSpecificationId
                FROM TargetTable
                WHERE VariantType IS NOT NULL AND Visibility IS NOT NULL
            );

-- Declare variables to hold values used for updates
DECLARE @min            int, 
        @max            int,
        @ID             uniqueidentifier,
        @PriceListDate  varchar(8),
        @ProdClassID    varchar(10),
        @ModelID        varchar(20),
        @MarketID       varchar(10);

-- Set minimum and maximum values for looping. See comments in the notes section on top.
SELECT @min = 1,@max = MAX(RowID) From #CSpec;

-- Populate other variables in a loop
WHILE @min <= @max
BEGIN
    BEGIN TRY
    BEGIN TRANSACTION;
    SELECT 
        @ID = ID,
        @PriceListDate = PriceListDate,
        @ProdClassID = ProdClassID,
        @ModelID = ModelID,
        @MarketID = MarketID
    FROM #CSpec
    WHERE RowID = @min;  

-- Use CTE to get relevant values from SourceTable1
    ;WITH CTE AS
    (
    SELECT  V.variantid, 
            V.varianttype, 
            V.visibility,
            MAX(V.PriceListVersion) LatestPriceVersion
    FROM    SourceTable1 V 
    WHERE       V.ModelID = @ModelID
            AND V.ProdClassID = @ProdClassID
            AND V.PriceListDate = @PriceListDate
            AND V.MarketID = @MarketID
    GROUP BY
            V.variantid, 
            V.varianttype, 
            V.visibility
    )

-- Update the TargetTable with the values obtained in the CTE
    UPDATE      SV 
    SET         SV.VariantType = VC.VariantType, 
                SV.Visibility = VC.Visibility
    FROM        spec_variant SV 
    INNER JOIN  CTE VC
    ON          SV.VariantId = VC.VariantId
    WHERE       SV.ChassisSpecificationId = @ID
                AND SV.VariantType IS NULL
                AND SV.Visibility IS NULL;

   -- Check for errors and commit transaction
        IF @@ERROR = 0
            BEGIN
                COMMIT TRANSACTION;
                 -- Increment the value of loop variable
                SET @min = @min+1;
            END
    END TRY
    BEGIN CATCH
        IF @@ERROR <> 0
            BEGIN
                ROLLBACK;
            END
    END CATCH
END
-- Clean up
SET NOCOUNT OFF; 
DROP TABLE #CSpec;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.