AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 40580
Accepted
Raj
Raj
Asked: 2013-04-22 21:48:54 +0800 CST2013-04-22 21:48:54 +0800 CST 2013-04-22 21:48:54 +0800 CST

Atualizando uma tabela com mais de 850 milhões de linhas de dados

  • 772

Fui encarregado de escrever uma consulta de atualização para atualizar uma tabela com mais de 850 milhões de linhas de dados. Aqui estão as estruturas da tabela:

Tabelas de origem:

    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]

SourceTable1contém 52 milhões de linhas de dados e SourceTable2contém 400.000 linhas de dados.

Aqui está a TargetTableestrutura

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]

A relação entre essas tabelas é a seguinte:

  • SourceTable1.VariantIDestá relacionado aTargetTable.VariantID
  • SourceTable2.IDestá relacionado aTargetTable.ChassisSpecificationId

O requisito de atualização é o seguinte:

  1. Obtenha os valores de e VariantTypepara cada , tendo o valor máximo na coluna.VisibilitySourceTable1VariantIDPriceListVersion
  2. Obtenha o valor da IDcoluna de SourceTable2onde os valores de , ModelIDe correspondem aos de .ProdClassIDPriceListDateMarketIDSourceTable1
  3. Agora atualize o TargetTablecom os valores para VariantTypee Visibilityonde as ChassisspecificationIDcorrespondências SourceTable2.IDe VariantIDcorrespondênciasSourceTable1.VariantID

O desafio é fazer essa atualização na produção ao vivo, com o mínimo de travamento. Aqui está a consulta que montei.

-- 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

Demora cerca de 30 segundos quando defino o limite de iterações para 10, codificando o valor da @maxvariável. No entanto, quando aumento o limite para 50 iterações, leva quase 4 minutos para ser concluído. Estou preocupado que o tempo de execução necessário para 400.000 iterações será executado em vários dias na produção. No entanto, isso ainda pode ser aceitável, se o TargetTablenão for bloqueado, impedindo que os usuários o acessem.

Todas as entradas são bem-vindas.

Obrigado, Raj

sql-server sql-server-2008-r2
  • 2 2 respostas
  • 8826 Views

2 respostas

  • Voted
  1. Best Answer
    gbn
    2013-04-23T00:20:32+08:002013-04-23T00:20:32+08:00

    Para acelerar as coisas, você pode tentar

    • Adicionar uma chave primária a #CSpec.RowID para que você não a verifique a cada iteração
    • Altere o CTE para uma tabela temporária com PK adequado. Veja o próximo ponto também
    • Adicione um índice em SourceTable1 para corresponder à cláusula CTE WHERE: atualmente, o PK será verificado, o que significa que todas as linhas de SourceTable1 serão verificadas a cada iteração. Todas as 52 milhões de linhas
    • SourceTable2.MarketID também não possui um índice, mas não me preocuparia com isso porque é verificado apenas uma vez (pelo que entendi)

    Os planos de consulta aqui devem mostrar muitas varreduras porque você tem índices ruins para as operações que está fazendo.

    A indexação da tabela de destino parece OK

    Outra observação: uniqueidentifier e varchar são escolhas ruins para índices clusterizados (seus PKs aqui): muito amplo, não aumentando, sobrecarga de comparações de coleção, pelo menos

    Editar, outra observação (graças a @Marian)

    Seu índice clusterizado geralmente é amplo. Cada índice não clusterizado aponta para o índice clusterizado, o que significa um enorme índice NC também

    Provavelmente , você poderia obter o mesmo resultado reordenando o PK agrupado.

    • 5
  2. Raj
    2013-04-25T19:44:42+08:002013-04-25T19:44:42+08:00

    Postando o SQL final desse processo, para benefício da comunidade

    /********************************************************************************************************************
    *  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;
    
    • 4

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Conceder acesso a todas as tabelas para um usuário

    • 5 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve