Ich wurde beauftragt, eine Aktualisierungsabfrage zu schreiben, um eine Tabelle mit mehr als 850 Millionen Datenzeilen zu aktualisieren. Hier sind die Tabellenstrukturen:
Quellentabellen:
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]
SourceTable1
enthält 52 Millionen Datenzeilen und SourceTable2
enthält 400.000 Datenzeilen.
Hier ist die TargetTable
Struktur
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]
Die Beziehung zwischen diesen Tabellen ist wie folgt:
SourceTable1.VariantID
bezieht sich aufTargetTable.VariantID
SourceTable2.ID
bezieht sich aufTargetTable.ChassisSpecificationId
Die Aktualisierungsanforderung lautet wie folgt:
- Holen Sie sich die Werte für
VariantType
undVisibility
vonSourceTable1
jedemVariantID
mit dem Maximalwert in derPriceListVersion
Spalte. - Holen Sie sich den Wert der
ID
Spalte vonSourceTable2
wo die Werte vonModelID
,ProdClassID
,PriceListDate
undMarketID
Übereinstimmung mit derSourceTable1
. - Aktualisieren Sie nun die
TargetTable
mit den Werten fürVariantType
undVisibility
wo dieChassisspecificationID
ÜbereinstimmungenSourceTable2.ID
undVariantID
ÜbereinstimmungenSourceTable1.VariantID
Die Herausforderung besteht darin, dieses Update für die Live-Produktion mit minimaler Sperrung durchzuführen. Hier ist die Abfrage, die ich zusammengestellt habe.
-- 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
Es dauert ungefähr 30 Sekunden, wenn ich das Limit der Iterationen auf 10 setze, indem ich den Wert der @max
Variablen fest codiere . Wenn ich jedoch das Limit auf 50 Iterationen erhöhe, dauert der Abschluss fast 4 Minuten. Ich befürchte, dass die Ausführungszeit für 400.000 Iterationen bei der Produktion mehrere Tage betragen wird. Dies ist jedoch möglicherweise noch akzeptabel, wenn das TargetTable
nicht gesperrt wird und Benutzer nicht darauf zugreifen können.
Alle Eingaben sind willkommen.
Danke, Raj
Antworten:
Um die Dinge zu beschleunigen, könnten Sie versuchen
Die Abfragepläne hier sollten viele Scans anzeigen, da Sie schlechte Indizes für die von Ihnen ausgeführten Vorgänge haben.
Die Indizierung der Zieltabelle wird in Ordnung angezeigt
Eine weitere Beobachtung: Uniqueidentifier und Varchar sind eine schlechte Wahl für Clustered-Indizes (Ihre PKs hier): Zumindest der Aufwand für Sammlungsvergleiche ist zu groß, nicht erhöht
Bearbeiten, eine weitere Beobachtung (danke an @Marian)
Ihr Clustered-Index ist im Allgemeinen breit. Jeder nicht gruppierte Index zeigt auf den gruppierten Index, was auch einen riesigen NC-Index bedeutet
Sie könnten wahrscheinlich das gleiche Ergebnis erzielen, indem Sie die Cluster-PK neu anordnen.
quelle
Veröffentlichung der endgültigen SQL für diesen Prozess zum Nutzen der Community
quelle