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 / user-18561

JohnnyM's questions

Martin Hope
JohnnyM
Asked: 2014-12-18 10:30:53 +0800 CST

No SQL Server, posso garantir uma ordem sem uma cláusula ORDER BY explícita quando uma busca de índice é forçada em uma tabela com apenas um índice clusterizado?

  • 27

Atualização 2014-12-18

Com a resposta esmagadora à pergunta principal sendo "Não", as respostas mais interessantes se concentraram na parte 2, como resolver o quebra-cabeça de desempenho com um ORDER BY. Embora eu já tenha marcado uma resposta, não ficaria surpreso se houvesse uma solução com desempenho ainda melhor.

Original

Essa dúvida surgiu porque a única solução extremamente rápida que consegui encontrar para um determinado problema só funciona sem uma ORDER BYcláusula. Abaixo está o T-SQL completo necessário para produzir o problema, junto com minha solução proposta (estou usando o SQL Server 2008 R2, se isso for importante).

--Create Orders table
IF OBJECT_ID('tempdb..#Orders') IS NOT NULL DROP TABLE #Orders
CREATE TABLE #Orders
(  
       OrderID    INT NOT NULL IDENTITY(1,1)
     , CustID     INT NOT NULL
     , StoreID    INT NOT NULL       
     , Amount     FLOAT NOT NULL
)
CREATE CLUSTERED INDEX IX ON #Orders (StoreID, Amount DESC, CustID)

--Add 1 million rows w/ 100K Customers each of whom had 10 orders
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO #Orders (CustID, StoreID, Amount)
SELECT CustID = Number / 10
     , StoreID    = Number % 4
     , Amount     = 1000 * RAND(Number)
FROM  FinalCte
WHERE Number <= 1000000

SET STATISTICS IO ON
SET STATISTICS TIME ON

--For StoreID = 1, find the top 500 customers ordered by their most expensive purchase (Amount)

--Solution A: Without ORDER BY
DECLARE @Top INT = 500
SELECT DISTINCT TOP (@Top) CustID
FROM #Orders WITH(FORCESEEK)
WHERE StoreID = 1
OPTION(OPTIMIZE FOR (@Top = 1), FAST 1);
--9 logical reads, CPU Time = 0 ms, elapsed time = 1 ms
GO
--Solution B: With ORDER BY
DECLARE @Top INT = 500
SELECT TOP (@Top) CustID
FROM #Orders
WHERE StoreID = 1
GROUP BY CustID
ORDER BY MAX(Amount) DESC
OPTION(MAXDOP 1)
--745 logical reads, CPU Time = 141 ms, elapsed time = 145 ms
--Uses Sort operator

GO

Aqui estão os planos de execução para a Solução A e B, respectivamente:

Sol A

Sol B

A solução A fornece o desempenho de que preciso, mas não consegui fazê-la funcionar com o mesmo desempenho ao adicionar qualquer tipo de cláusula ORDER BY (por exemplo, consulte a Solução B). E certamente parece que a Solução A teria que entregar seus resultados em ordem, já que 1) a tabela tem apenas um índice nela, 2) uma busca é forçada, eliminando assim a possibilidade de usar uma varredura de ordem de alocação baseada em páginas IAM .

Então minhas perguntas são:

  1. Estou certo de que garantirá o pedido neste caso sem uma cláusula de pedido?

  2. Caso contrário, existe outro método para forçar um plano que seja tão rápido quanto a Solução A, preferencialmente um que evite classificações? Observe que ele teria que resolver exatamente o mesmo problema (para StoreID = 1, encontre os 500 principais clientes solicitados pelo valor de compra mais caro). Também teria que usar a #Orderstabela, mas diferentes esquemas de indexação seriam OK.

sql-server
  • 2 respostas
  • 7909 Views
Martin Hope
JohnnyM
Asked: 2014-04-19 11:29:12 +0800 CST

No SQL Server, devo forçar um LOOP JOIN no seguinte caso?

  • 15

Normalmente, eu não recomendo o uso de dicas de junção por todos os motivos padrão. Recentemente, no entanto, descobri um padrão em que quase sempre encontro uma junção de loop forçada para ter um desempenho melhor. Na verdade, estou começando a usar e recomendo tanto que queria uma segunda opinião para ter certeza de que não estou perdendo nada. Aqui está um cenário representativo (o código muito específico para gerar um exemplo está no final):

--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID

--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID

SampleTable tem 1 milhão de linhas e seu PK é ID.
A tabela temporária #Driver tem apenas uma coluna, ID, nenhum índice e 50 mil linhas.

O que eu sempre encontro é o seguinte:

Caso 1: NO HINT
Index Scan na SampleTable
Hash Join
Maior duração (média de 333 ms)
Maior CPU (média de 331 ms)
Baixas leituras lógicas (4714)

Caso 2: LOOP JOIN HINT
Index Seek on SampleTable
Loop Join
Menor duração (média de 204ms, 39% menos)
CPU mais baixa (média de 206, 38% menos)
Leituras lógicas muito mais altas (160015, 34X mais)

A princípio, as leituras muito mais altas do segundo caso me assustaram um pouco porque diminuir as leituras costuma ser considerado uma medida decente de desempenho. Mas quanto mais penso sobre o que está realmente acontecendo, isso não me preocupa. Aqui está o meu pensamento:

SampleTable está contido em 4714 páginas, ocupando cerca de 36 MB. O caso 1 verifica todos eles, e é por isso que obtemos 4714 leituras. Além disso, ele deve executar 1 milhão de hashes, que consomem muita CPU e, em última análise, aumentam o tempo proporcionalmente. É todo esse hashing que parece aumentar o tempo no caso 1.

Agora considere o caso 2. Ele não está fazendo nenhum hash, mas em vez disso está fazendo 50.000 buscas separadas, que é o que está aumentando as leituras. Mas quão caras são as leituras comparativamente? Pode-se dizer que, se essas forem leituras físicas, pode ser muito caro. Mas lembre-se de 1) apenas a primeira leitura de uma determinada página pode ser física e 2) mesmo assim, o caso 1 teria o mesmo ou pior problema, pois é garantido que atingirá todas as páginas.

Levando em conta o fato de que ambos os casos precisam acessar cada página pelo menos uma vez, parece ser uma questão de qual é mais rápido, 1 milhão de hashes ou cerca de 155.000 leituras na memória? Meus testes parecem dizer o último, mas o SQL Server escolhe consistentemente o primeiro.

Pergunta

Então, de volta à minha pergunta: devo continuar forçando essa dica LOOP JOIN quando o teste mostra esses tipos de resultados ou estou perdendo alguma coisa na minha análise? Estou hesitante em ir contra o otimizador do SQL Server, mas parece que ele muda para usar uma junção de hash muito antes do que deveria em casos como esses.

Atualização 2014-04-28

Fiz mais alguns testes e descobri que os resultados que estava obtendo acima (em uma VM com 2 CPUs) não podiam ser replicados em outros ambientes (tentei em 2 máquinas físicas diferentes com 8 e 12 CPUs). O otimizador se saiu muito melhor nos últimos casos, a ponto de não haver esse problema pronunciado. Acho que a lição aprendida, que parece óbvia em retrospecto, é que o ambiente pode afetar significativamente o funcionamento do otimizador.

Planos de Execução

Plano de Execução Caso 1 Plano 1 Plano de Execução Caso 2 insira a descrição da imagem aqui

Código para gerar caso de exemplo

------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------    

CREATE TABLE SampleTable
    (  
       ID         INT NOT NULL PRIMARY KEY CLUSTERED
     , Number1    INT NOT NULL
     , Number2    INT NOT NULL
     , Number3    INT NOT NULL
     , Number4    INT NOT NULL
     , Number5    INT NOT NULL
    )

--Add 1 million rows
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM  FinalCte
WHERE Number <= 1000000

------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------    
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO

------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------    
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0

------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------    

GO
/*Reg*/  EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint


------------------------------------------------------------
-- Results
------------------------------------------------------------    

/*

Duration CPU   Reads    TextData
315      313   4714     /*Reg*/  EXEC JoinTest_NoHint
309      296   4713     /*Reg*/  EXEC JoinTest_NoHint
327      329   4713     /*Reg*/  EXEC JoinTest_NoHint
398      406   4715     /*Reg*/  EXEC JoinTest_NoHint
316      312   4714     /*Reg*/  EXEC JoinTest_NoHint
217      219   160017   /*Loop*/ EXEC JoinTest_LoopHint
211      219   160014   /*Loop*/ EXEC JoinTest_LoopHint
217      219   160013   /*Loop*/ EXEC JoinTest_LoopHint
190      188   160013   /*Loop*/ EXEC JoinTest_LoopHint
187      187   160015   /*Loop*/ EXEC JoinTest_LoopHint

*/
sql-server
  • 2 respostas
  • 8181 Views
Martin Hope
JohnnyM
Asked: 2013-01-29 16:07:24 +0800 CST

O "número estimado de linhas" é sempre por execução nos planos de execução do SSMS?

  • 7

Quando olho para os planos de execução, geralmente comparo "Número estimado de linhas" com "Número real de linhas", procurando discrepâncias. Por exemplo, quando passo o mouse sobre a seta inferior na figura abaixo, a estimativa é 1 e a real é 200, portanto, à primeira vista, parece ser uma estimativa terrível.

insira a descrição da imagem aqui

No entanto, comecei a me perguntar se essa é realmente a estimativa para apenas uma execução. Quando passo o mouse sobre a busca para obter mais detalhes, vejo que há 200 execuções e estima-se 199,98. Portanto, se meu palpite estiver correto, a estimativa está correta.

insira a descrição da imagem aqui

Alguém pode me confirmar se é isso mesmo? E se sim, sou o único a pensar que isso é incrivelmente enganoso (especialmente ao passar o mouse sobre as setas)? Se ajudar, aqui está o script para configurar e reproduzir o plano acima:

-- Setup: Create 2 tables with 100K rows each.

CREATE TABLE T1(Id int NOT NULL PRIMARY KEY, X INT NOT NULL) 
CREATE TABLE T2(Id int NOT NULL PRIMARY KEY) 
CREATE INDEX Ix ON T1 (X, Id)

;WITH  
    Pass0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Pass1 AS (SELECT 1 AS C FROM Pass0 AS A, Pass0 AS B),--4 rows  
    Pass2 AS (SELECT 1 AS C FROM Pass1 AS A ,Pass1 AS B),--16 rows 
    Pass3 AS (SELECT 1 AS C FROM Pass2 AS A ,Pass2 AS B),--256 rows 
    Pass4 AS (SELECT 1 AS C FROM Pass3 AS A ,Pass3 AS B),--65536 rows 
    Pass5 AS (SELECT 1 AS C FROM Pass4 AS A ,Pass2 AS B),--1048576 rows 
    Tally AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Pass5)
INSERT INTO T1
SELECT Number, Number
FROM  Tally
WHERE Number <= 100000

INSERT INTO T2
SELECT Id
FROM T1

--Now join them

SELECT *
FROM T1
JOIN T2 ON T2.Id = T1.Id
WHERE T1.X <= 200
sql-server ssms
  • 2 respostas
  • 5817 Views

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