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 / 182410
Accepted
Cory Nelson
Cory Nelson
Asked: 2017-08-02 11:45:34 +0800 CST2017-08-02 11:45:34 +0800 CST 2017-08-02 11:45:34 +0800 CST

Forçando fluxo distinto

  • 772

Tenho uma tabela assim:

CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    ObjectId INT NOT NULL
)

Essencialmente, rastreando atualizações de objetos com um ID crescente.

O consumidor desta tabela selecionará um bloco de 100 IDs de objetos distintos, ordenados por UpdateIde a partir de um UpdateId. Essencialmente, mantendo o controle de onde parou e, em seguida, consultando as atualizações.

Descobri que isso é um problema de otimização interessante porque só consegui gerar um plano de consulta maximamente ideal escrevendo consultas que fazem o que eu quero devido aos índices, mas não garantem o que eu quero:

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

Onde @fromUpdateIdé um parâmetro de procedimento armazenado.

Com um plano de:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

Devido à busca no UpdateIdíndice que está sendo usado, os resultados já são bons e ordenados do menor para o maior ID de atualização, como eu quero. E isso gera um plano de fluxo distinto , que é o que eu quero. Mas o ordenamento obviamente não é um comportamento garantido, então não quero usá-lo.

Esse truque também resulta no mesmo plano de consulta (embora com um TOP redundante):

WITH ids AS
(
    SELECT ObjectId
    FROM Updates
    WHERE UpdateId > @fromUpdateId
    ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

No entanto, não tenho certeza (e suspeito que não) se isso realmente garante o pedido.

Uma consulta que eu esperava que o SQL Server fosse inteligente o suficiente para simplificar era essa, mas acaba gerando um plano de consulta muito ruim:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

Com um plano de:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

Estou tentando encontrar uma maneira de gerar um plano ideal com uma busca de índice ativada UpdateIde um fluxo distinto para remover ObjectIds duplicados. Alguma ideia?

Dados de amostra, se você quiser. Os objetos raramente terão mais de uma atualização e quase nunca devem ter mais de uma em um conjunto de 100 linhas, e é por isso que estou atrás de um fluxo distinct , a menos que haja algo melhor que eu não conheça? No entanto, não há garantia de que um único ObjectIdnão terá mais de 100 linhas na tabela. A tabela tem mais de 1.000.000 de linhas e espera-se que cresça rapidamente.

Suponha que o usuário deste tenha outra maneira de encontrar o próximo apropriado @fromUpdateId. Não há necessidade de devolvê-lo nesta consulta.

sql-server sql-server-2014
  • 3 3 respostas
  • 853 Views

3 respostas

  • Voted
  1. Best Answer
    Paul White
    2017-08-03T00:57:53+08:002017-08-03T00:57:53+08:00

    O otimizador do SQL Server não pode produzir o plano de execução desejado com a garantia necessária, porque o operador Hash Match Flow Distinct não preserva a ordem.

    No entanto, não tenho certeza (e suspeito que não) se isso realmente garante o pedido.

    Você pode observar a preservação da ordem em muitos casos, mas este é um detalhe de implementação; não há garantia, então você não pode confiar nele. Como sempre, a ordem de apresentação só pode ser garantida por uma ORDER BYcláusula de nível superior.

    Exemplo

    O script abaixo mostra que Hash Match Flow Distinct não preserva a ordem. Ele configura a tabela em questão com números correspondentes de 1 a 50.000 em ambas as colunas:

    IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
        DROP TABLE dbo.Updates;
    GO
    CREATE TABLE Updates
    (
        UpdateId INT NOT NULL IDENTITY(1,1),
        ObjectId INT NOT NULL,
    
        CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
    );
    GO
    INSERT dbo.Updates (ObjectId)
    SELECT TOP (50000)
        ObjectId =
            ROW_NUMBER() OVER (
                ORDER BY C1.[object_id]) 
    FROM sys.columns AS C1
    CROSS JOIN sys.columns AS C2
    ORDER BY
        ObjectId;
    

    A consulta de teste é:

    DECLARE @Rows bigint = 50000;
    
    -- Optimized for 1 row, but will be 50,000 when executed
    SELECT DISTINCT TOP (@Rows)
        U.ObjectId 
    FROM dbo.Updates AS U
    WHERE 
        U.UpdateId > 0
    OPTION (OPTIMIZE FOR (@Rows = 1));
    

    O plano estimado mostra um índice de busca e fluxo distinto:

    Plano estimado

    A saída certamente parece ordenada para começar:

    Início dos resultados

    ...mas os valores mais abaixo começam a 'faltar':

    Quebra de padrão

    ...e eventualmente:

    O caos irrompe

    A explicação, neste caso específico, é que o operador de hash derrama:

    Plano pós-execução

    Uma vez que uma partição se espalha, todas as linhas que fazem hash para a mesma partição também se espalham. As partições derramadas são processadas posteriormente, quebrando a expectativa de que valores distintos encontrados sejam emitidos imediatamente na sequência em que são recebidos.


    Há muitas maneiras de escrever uma consulta eficiente para produzir o resultado ordenado que você deseja, como recursão ou usando um cursor. No entanto, isso não pode ser feito usando Hash Match Flow Distinct .

    • 15
  2. Joe Obbish
    2017-08-02T14:28:30+08:002017-08-02T14:28:30+08:00

    Estou insatisfeito com esta resposta porque não consegui obter um operador distinto de fluxo junto com resultados que eram garantidos como corretos. No entanto, tenho uma alternativa que deve obter um bom desempenho junto com resultados corretos. Infelizmente, isso requer que um índice não clusterizado seja criado na tabela.

    Abordei esse problema tentando pensar em uma combinação de colunas que pudesse ORDER BYe obter os resultados corretos depois de aplicar DISTINCTa elas. O valor mínimo de UpdateIdper ObjectIdjunto com ObjectIdé uma dessas combinações. No entanto, pedir diretamente o mínimo UpdateIdparece resultar na leitura de todas as linhas da tabela. Em vez disso, podemos solicitar indiretamente o valor mínimo de UpdateIdcom outra junção à tabela. A ideia é varrer a Updatestabela em ordem, descartar todas as linhas para as quais UpdateIdnão é o valor mínimo para essa linha ObjectIde manter as primeiras 100 linhas. Com base em sua descrição da distribuição de dados, não deveríamos precisar descartar muitas linhas.

    Para preparação de dados, coloquei 1 milhão de linhas em uma tabela com 2 linhas para cada ObjectId distinto:

    INSERT INTO Updates WITH (TABLOCK)
    SELECT t.RN / 2
    FROM 
    (
        SELECT TOP 1000000 -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
        FROM master..spt_values t1
        CROSS JOIN master..spt_values t2
    ) t;
    
    CREATE INDEX IX On Updates (Objectid, UpdateId);
    

    O índice não clusterizado em Objectide UpdateIdé importante. Ele nos permite descartar com eficiência linhas que não têm o mínimo UpdateIdpor Objectid. Há muitas maneiras de escrever uma consulta que corresponda à descrição acima. Aqui está uma maneira usando NOT EXISTS:

    DECLARE @fromUpdateId INT = 9999;
    SELECT ObjectId
    FROM (
        SELECT DISTINCT TOP 100 u1.UpdateId, u1.ObjectId
        FROM Updates u1
        WHERE UpdateId > @fromUpdateId
        AND NOT EXISTS (
            SELECT 1
            FROM Updates u2
            WHERE u2.UpdateId > @fromUpdateId
            AND u1.ObjectId = u2.ObjectId
            AND u2.UpdateId < u1.UpdateId
        )
        ORDER BY u1.UpdateId, u1.ObjectId
    ) t;
    

    Aqui está uma imagem do plano de consulta :

    plano de consulta

    Na melhor das hipóteses, o SQL Server fará apenas 100 buscas de índice em relação ao índice não clusterizado. Para simular ter muito azar eu mudei a consulta para retornar as primeiras 5000 linhas para o cliente. Isso resultou em 9.999 buscas de índice, então é como obter uma média de 100 linhas por ObjectId. Aqui está a saída de SET STATISTICS IO, TIME ON:

    Tabela 'Atualizações'. Contagem de varredura 10.000, leituras lógicas 31.900, leituras físicas 0

    Tempos de execução do SQL Server: tempo de CPU = 31 ms, tempo decorrido = 42 ms.

    • 11
  3. Rob Farley
    2017-08-02T17:32:36+08:002017-08-02T17:32:36+08:00

    Eu amo a pergunta - Flow Distinct é um dos meus operadores favoritos.

    Agora, a garantia é o problema. Quando você pensa no operador FD puxando linhas do operador Seek de forma ordenada, produzindo cada linha conforme determina que ela seja única, isso lhe dará as linhas na ordem correta. Mas é difícil saber se pode haver alguns cenários em que o FD não lida com uma única linha de cada vez.

    Teoricamente, o FD poderia solicitar 100 linhas do Seek e produzi-las na ordem que precisar.

    As dicas de consulta OPTION (FAST 1, MAXDOP 1)podem ajudar, porque evitarão obter mais linhas do que o necessário do operador Seek. Mas é uma garantia ? Não exatamente. Ele ainda pode decidir puxar uma página de linhas de cada vez, ou algo assim.

    Acho que com OPTION (FAST 1, MAXDOP 1), sua OFFSETversão lhe daria muita confiança sobre o pedido, mas não é uma garantia.

    • 9

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

    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