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 / 177083
Accepted
Pரதீப்
Pரதீப்
Asked: 2017-06-23 20:32:36 +0800 CST2017-06-23 20:32:36 +0800 CST 2017-06-23 20:32:36 +0800 CST

Três consultas de atualização versus desempenho de consulta de atualização única

  • 772

Estou tentando otimizar um procedimento. Existem 3 consultas de atualização diferentes presentes no procedimento.

update #ResultSet
set MajorSector = case 
        when charindex('  ', Sector) > 2 then rtrim(ltrim(substring(Sector, 0, charindex('  ', Sector)))) 
            else ltrim(rtrim(sector)) 
        end

update #ResultSet
set MajorSector = substring(MajorSector, 5, len(MajorSector)-4)
where left(MajorSector,4) in ('(00)','(01)','(02)','(03)','(04)','(05)','(06)','(07)','(08)','(09)')

update #ResultSet
set MajorSector = substring(MajorSector, 4, len(MajorSector)-3)
where left(MajorSector,3) in ('(A)','(B)','(C)','(D)','(E)','(F)','(G)','(H)','(I)','(J)','(K)','(L)','(M)','(N)','(O)','(P)','(Q)','(R)','(S)','(T)','(U)','(V)','(W)','(X)','(Y)','(Z)')

Para concluir todas as três consultas de atualização, leva menos de 10 segundos .

Plano de execução para todas as três consultas de atualização.

https://www.brentozar.com/pastetheplan/?id=r11BLfq7b

O que planejei é mudar as três consultas de atualização diferentes em uma única consulta de atualização, para que a E/S possa ser reduzida.

;WITH ResultSet
     AS (SELECT CASE
                  WHEN LEFT(temp_MajorSector, 4) IN ( '(00)', '(01)', '(02)', '(03)', '(04)', '(05)', '(06)', '(07)', '(08)', '(09)' ) 
                      THEN Substring(temp_MajorSector, 5, Len(temp_MajorSector) - 4)
                  WHEN LEFT(temp_MajorSector, 3) IN ( '(A)', '(B)', '(C)', '(D)','(E)', '(F)', '(G)', '(H)','(I)', '(J)', '(K)', '(L)','(M)', '(N)', '(O)', '(P)','(Q)', '(R)', '(S)', '(T)','(U)', '(V)', '(W)', '(X)','(Y)', '(Z)' ) 
                      THEN Substring(temp_MajorSector, 4, Len(temp_MajorSector) - 3)
                  ELSE temp_MajorSector
                END AS temp_MajorSector,
                MajorSector
         FROM   (SELECT temp_MajorSector = CASE
                                             WHEN Charindex('  ', Sector) > 2 THEN Rtrim(Ltrim(Substring(Sector, 0, Charindex('  ', Sector))))
                                             ELSE Ltrim(Rtrim(sector))
                                           END,
                        MajorSector
                 FROM   #ResultSet)a)
UPDATE ResultSet
SET    MajorSector = temp_MajorSector  

Mas isso leva cerca de 1 minuto para ser concluído. Verifiquei o plano de execução, é idêntico à primeira consulta de atualização .

Plano de execução para a consulta acima:

https://www.brentozar.com/pastetheplan/?id=SJvttz9QW

Alguém pode explicar porque é lento?

Dados fictícios para teste:

If object_id('tempdb..#ResultSet') is not null
drop table #ResultSet


;WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0)
    ,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4
    ,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16
    ,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256
    ,lv4 AS (SELECT 0 g FROM lv3 a CROSS JOIN lv3 b) -- 65,536
    ,lv5 AS (SELECT 0 g FROM lv4 a CROSS JOIN lv4 b) -- 4,294,967,296
    ,Tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv5)
SELECT CONVERT(varchar(255), NEWID()) as Sector,cast('' as varchar(1000)) as MajorSector
into #ResultSet
FROM Tally
where  n <= 242906 -- my original table record count
ORDER BY n;

Nota: Como não são meus dados originais, os horários que mencionei acima podem ser um pouco diferentes. Ainda assim, a consulta de atualização única é muito mais lenta que as três primeiras.

Tentei executar as consultas mais de 10 vezes para garantir que fatores externos não afetem o desempenho. Todas as 10 primeiras três atualizações foram executadas muito mais rapidamente do que a última atualização única.

sql-server performance
  • 3 3 respostas
  • 724 Views

3 respostas

  • Voted
  1. Best Answer
    Michael Green
    2017-06-23T21:56:45+08:002017-06-23T21:56:45+08:00

    A primeira única atualização lê e grava todas as linhas da tabela. A segunda e a terceira leem e reescrevem um subconjunto dessas linhas. Olhe para o Actual Number of Rows. Quando as três instruções são combinadas em uma, o otimizador calcula que, se ele tiver que ler tudo para satisfazer a primeira alteração, ele poderá aproveitar isso para a segunda e a terceira alteração.

    Dê uma olhada na versão XML dos planos de consulta, especificamente os <ComputeScalar>operadores e <ScalarOperator ScalarString="">partes. No plano original, você verá que cada um é relativamente simples e mapeia muito de perto o SQL. Para o plano tudo-em-um é um monstro. Este é o otimizador reescrevendo o SQL em uma forma logicamente equivalente. Um plano funciona 1 passando cada linha pelos operadores uma vez. O otimizador está fazendo todo o trabalho necessário para satisfazer todas as três alterações à medida que essa linha passa uma vez.

    Eu esperaria que a segunda consulta fosse mais rápida porque os dados são lidos e gravados apenas uma vez, enquanto na primeira são tocados três vezes.

    Como a segunda consulta não tem predicados (sem cláusula WHERE), o otimizador não tem escolha a não ser ler cada linha e processá-la. Estou surpreso que a segunda forma demore mais do que a primeira. Ambos estão começando a partir de buffers limpos? Há outro trabalho acontecendo no sistema? Como está lendo e gravando em uma tabela temporária, o IO está acontecendo no tempdb. Existe crescimento de arquivo ou algo assim acontecendo?

    Por uma medida, você alcançou o resultado desejado. Você diz que deseja fazer alterações "para que o IO possa ser reduzido". O all-in-one faz menos IO do que as três instruções separadas no total. Eu suspeito que o que você realmente quer, no entanto, é reduzir o tempo decorrido, e isso obviamente não está acontecendo.


    1 mais ou menos, muitos detalhes omitidos.


    Executei sua rotina para gerar dados de teste e executei as três instruções de atualização única e a instrução tudo-em-um. Embora existam algumas diferenças (sem índice clusterizado, sem paralelismo), recebo mais ou menos os mesmos resultados. Especificamente, os planos têm aproximadamente a mesma forma e as três consultas individuais são concluídas em cerca de dois segundos e a única consulta grande em cerca de trinta a trinta e cinco segundos.

    eu coloco

    set nocount off;
    set statistics io on;
    set statistics time on;
    

    Com o plano em cache e os dados na memória, recebo:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 0 ms.
    Table '#ResultSet...'. Scan count 1, logical reads 125223, physical reads 0
    
     SQL Server Execution Times:
       CPU time = 1422 ms,  elapsed time = 1417 ms.
    
    (242906 row(s) affected)
    
    Table '#ResultSet...'. Scan count 1, logical reads 125223, physical reads 0
    
     SQL Server Execution Times:
       CPU time = 344 ms,  elapsed time = 337 ms.
    
    (0 row(s) affected)
    
    Table '#ResultSet...'. Scan count 1, logical reads 125223, physical reads 0
    
     SQL Server Execution Times:
       CPU time = 734 ms,  elapsed time = 747 ms.
    
    (0 row(s) affected)
    

    Eu removi alguns bits que não são relevantes. Como physical readsé zero para todos os três, a tabela cabe na memória. logical readsé o mesmo para os três, o que faz sentido. Como não há índices, a única abordagem é varrer todas as linhas da tabela. A segunda e a terceira consulta afetam zero linhas porque eu já as executei algumas vezes. O tempo de CPU e o trabalho decorrido são de 2500ms.

    Para a consulta maior é

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 0 ms.
    Table '#ResultSet...'. Scan count 1, logical reads 125223
    
     SQL Server Execution Times:
       CPU time = 33093 ms,  elapsed time = 33137 ms.
    
    (242906 row(s) affected)
    

    O mesmo número de páginas é lido, o mesmo número de linhas é atualizado. A grande diferença é o tempo de CPU. Isso se reflete na observação casual do Gerenciador de Tarefas, que mostra 30% de utilização durante a execução da consulta. A questão é, por que é preciso tanto?

    As consultas individuais separadamente têm cálculos simples e duas das instruções têm predicados que reduzem bastante o número de linhas tocadas. O otimizador tem boas heurísticas para processá-las e encontra um plano rápido. A consulta tudo-em-um aplica o monstroCompute Scalarcontra cada linha. Minha sugestão é que, por qualquer motivo, o otimizador não pode desvendar a lógica em um plano que seja rápido de executar e acabe usando muita CPU. O otimizador tem que trabalhar com o que é fornecido, que no segundo caso é SQL complexo e aninhado. Talvez refatorando o SQL o otimizador siga diferentes heurísticas e alcance um resultado melhor? Talvez alguns índices (filtrados) ou estatísticas (filtradas) o convençam a escrever um plano diferente. Talvez colunas computadas persistentes ajudassem. Talvez você só precise dar ao otimizador o que ele precisa e sua primeira tentativa realmente é a melhor que pode ser alcançada e você precisa encontrar uma maneira de executar esses três em paralelo. Desculpe, eu não posso ser mais científico.

    • 8
  2. Joe Obbish
    2017-07-05T11:19:00+08:002017-07-05T11:19:00+08:00

    Por favor, tenha mais cuidado com seus dados de teste no futuro. Os planos de consulta indicam que você tem um índice clusterizado em sua tabela, mas sua tabela temporária não possui um índice clusterizado. Isso pode fazer uma grande diferença em alguns casos. Na minha máquina, as três UPDATEabordagens são executadas em 3 segundos e a UPDATEabordagem única é executada em 5 segundos. Não perto da diferença que você vê, mas ainda parece um pouco contra-intuitivo. O single não deveria UPDATEser mais rápido?

    Como Michael Green apontou em sua resposta , o problema aqui é com o operador escalar de computação. O otimizador de consulta não é muito bom em estimar custos para escalares de computação. Os planos de consulta para a primeira atualização do conjunto de três e a segunda atualização solo podem parecer idênticos, mas há uma grande diferença em quanto trabalho o escalar de computação faz. Podemos realmente pegar o código e fazer algumas alterações para transformá-lo em uma SELECTconsulta válida. A consulta é enorme e o código completo está aqui . Abaixo está uma versão bastante abreviada:

    SELECT
      (CONVERT(varchar(1000), CASE
        WHEN SUBSTRING(CASE
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END, (1), (4)) = '(09)' OR
    ...
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END, (1), (4)) = '(00)' THEN SUBSTRING(CASE
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END, (5), LEN(CASE
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END) - (4))
        ELSE CASE
            WHEN SUBSTRING(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END, (1), (3)) = '(Z)' OR
    ...
              END, (1), (3)) = '(B)' OR
              SUBSTRING(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END, (1), (3)) = '(A)' THEN SUBSTRING(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END, (4), LEN(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END) - (3))
            ELSE CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END
          END
      END, 0))
    FROM [#ResultSet];
    

    Todos esses cálculos repetidos nas CASEdeclarações não são bons. Como parte da computação escalar, o SQL Server pode executar os mesmos cálculos repetidamente. Se eu executar isso como apenas um SELECT, leva cerca de 3 segundos, que é o tempo para o primeiro conjunto de UPDATEconsultas.

    Colocar cálculos escalares repetidos em uma APPLYtabela derivada geralmente pode melhorar a legibilidade das consultas. Em alguns casos, também pode melhorar drasticamente o desempenho. Peguei essa consulta grande e a simplifiquei um pouco movendo expressões repetidas para uma APPLYtabela derivada. Outras simplificações são possíveis, mas isso deve lhe dar a ideia básica:

    SELECT
      (CONVERT(varchar(1000), CASE
        WHEN a.sub4 IN ('(09)','(08)','(07)','(06)','(05)','(04)','(03)','(02)','(01)','(00)') 
            THEN SUBSTRING(CASE
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN a.r_trim
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END, (5), LEN(CASE
            WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN a.r_trim
            ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
          END) - (4))
        ELSE CASE
            WHEN a.sub3 IN ('(Z)','(Y)','(X)','(W)','(V)','(U)','(T)','(S)','(R)','(Q)','(P)','(O)','(N)','(M)','(L)','(K)','(J)','(I)','(H)','(G)','(F)','(E)','(D)','(C)','(B)','(A)')
            THEN SUBSTRING(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN a.r_trim
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END, (4), LEN(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN a.r_trim
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END) - (3))
            ELSE CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN a.r_trim
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END
          END
      END, 0))
    FROM [#ResultSet]
    OUTER APPLY 
    (
        SELECT SUBSTRING(CASE
                WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
              END, (1), (4))
        , SUBSTRING(CASE
                    WHEN CHARINDEX('  ', [#ResultSet].[Sector]) > (2) THEN RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
                    ELSE LTRIM(RTRIM([#ResultSet].[Sector]))
                  END, (1), (3))
        , RTRIM(LTRIM(SUBSTRING([#ResultSet].[Sector], (0), CHARINDEX('  ', [#ResultSet].[Sector]))))
    ) a (sub4, sub3, r_trim);
    

    Agora, a SELECTconsulta é executada em menos de 1 segundo. Eu usei OUTER APPLYpara que o SQL Server calculasse tudo na APPLYtabela derivada uma vez para cada linha, em vez de reduzi-la no escalar de computação. A computação escalar ainda está no plano de consulta, mas faz muito menos trabalho do que antes:

    estimado APLICAR

    Se eu conectar esse código ao CTE para sua UPDATEconsulta, recebo os seguintes números de desempenho:

    Tempo de CPU = 2125 ms, tempo decorrido = 2134 ms.

    Isso é um pouco mais rápido que o conjunto original de três consultas:

    Tempo de CPU = 1734 ms, tempo decorrido = 1735 ms

    Tempo de CPU = 187 ms, tempo decorrido = 197 ms.

    Tempo de CPU = 343 ms, tempo decorrido = 368 ms.

    Pode ser possível otimizar ainda mais a consulta individual, mas deixarei isso para você.

    • 7
  3. Robert Carnegie
    2017-06-24T06:16:17+08:002017-06-24T06:16:17+08:00

    Parece-me que a segunda e a terceira consultas podem ser reescritas usando estas fórmulas:

    MajorSector LIKE '(0[0-9])%'
    
    MajorSector LIKE '([A-Z])%'
    

    No entanto, isso não ajuda muito se MajorSectornão for indexado (e em uma tabela temporária, não for indexado) ou se estes constituírem todas as linhas da tabela.

    No entanto:

    1. Se o termo ( = dígito) ou ( = letra) ocorrer apenas no início da string, você poderá operar em all :'(0n)'n'(n)'nMajorSector

      REPLACE(REPLACE(REPLACE(MajorSector, '(00)', ''), '(01)', ''), '(02)', '')
      

      Mas com muito mais REPLACE (36).

      No entanto, isso se transformará '(00)A(01)B(02)C'em 'ABC'- não no que é desejado. Se esses dados não ocorrerem, considere-os.

    2. Se cada MajorSectorcomeça com '(0n)'ou '(n)'a ser removido - ou não contém nada ')'- então realmente você só precisa,

      SUBSTRING(MajorSector, CHARINDEX(')', MajorSector)+1, maxLength)
      

      onde maxLengthé o comprimento definido de, MajorSectorpor exemplo varchar(255). Se o parâmetro de comprimento em SUBSTRING()for maior que os dados reais, SUBSTRING()contenta-se em retornar os dados do deslocamento indicado até o final da string.

      SELECT SUBSTRING('ABCD', 3, 4) -- produces CD
      
      SELECT SUBSTRING('ABCD', 0, 4) -- produces ABC, confusing!
      
    • 1

relate perguntas

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

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

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

Sidebar

Stats

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

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

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

    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
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • 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
    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