Progresso da instrução SELECT INTO

14

Nosso fluxo de ETL possui uma instrução SELECT INTO de longa duração, que cria uma tabela rapidamente e a preenche com várias centenas de milhões de registros.

A declaração parece algo como SELECT ... INTO DestTable FROM SrcTable

Para fins de monitoramento, gostaríamos de ter uma idéia aproximada do progresso dessa instrução enquanto ela está sendo executada (aproximadamente número de linhas, número de bytes gravados ou semelhante).

Tentamos o seguinte sem sucesso:

-- Is blocked by the SELECT INTO statement:
select count(*) from DestTable with (nolock)

-- Returns 0, 0:
select rows, rowmodctr
from sysindexes with (nolock)
where id = object_id('DestTable')

-- Returns 0:
select rows
from sys.partitions
where object_id = object_id('DestTable')

Além disso, podemos ver a transação sys.dm_tran_active_transactions, mas não consegui encontrar uma maneira de obter a contagem de linhas afetadas em um dado transaction_id(algo semelhante a @@ROWCOUNTtalvez, mas com o transaction_idargumento as).

Entendo que no SQL Server a instrução SELECT INTO é uma instrução DDL e DML em uma e, como tal, a criação implícita da tabela será uma operação de bloqueio. Ainda acho que deve haver uma maneira inteligente de obter algum tipo de informação sobre o progresso enquanto a instrução está sendo executada.

Dan
fonte
Se você usasse uma tabela temporária global ## TABLE, poderia executar um Select com contagem na coluna de índice na ## TABLE para obter o número de registros já gravados e aproximar a quantidade total de registros a serem gravados?
CoveGeek 16/02

Respostas:

6

Eu suspeito que rowsin sys.partitionsé 0 devido a não ter sido confirmado ainda. Mas isso não significa que o SQL Server desconheça o que ocorrerá se a transação for confirmada. A chave é lembrar que todas as operações passam pelo Buffer Pool (ou seja, memória) primeiro, independentemente de COMMIT ou ROLLBACK da operação. Portanto, podemos procurar sys.dm_os_buffer_descriptorsessas informações:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

SELECT  --OBJECT_NAME(sp.[object_id]) AS [TableName], sdobd.*, '---', sp.*, '---', sau.*
       SUM(sdobd.[row_count]) AS [BufferPoolRows],
       SUM(sp.[rows]) AS [AllocatedRows],
       COUNT(*) AS [DataPages]
FROM sys.dm_os_buffer_descriptors sdobd
INNER JOIN  sys.allocation_units sau
        ON sau.[allocation_unit_id] = sdobd.[allocation_unit_id]
INNER JOIN  sys.partitions sp
        ON  (   sau.[type] = 1
            AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
        OR  (   sau.[type] = 2
            AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
        OR  (   sau.[type] = 3
            AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE   sdobd.[database_id] = DB_ID()
AND     sdobd.[page_type] = N'DATA_PAGE'
AND     sp.[object_id] = (SELECT so.[object_id]
                          FROM   sys.objects so
                          WHERE  so.[name] = 'TestDump')

Se você quiser ver os detalhes, remova o comentário da primeira linha de itens da SELECTlista e comente as 3 linhas restantes.

Testei executando o seguinte em uma sessão e executando repetidamente a consulta acima em outra.

SELECT so1.*
INTO   dbo.TestDump
FROM   sys.objects so1
CROSS JOIN sys.objects so2
CROSS JOIN sys.objects so3;
Solomon Rutzky
fonte
1
Isso é criativo. Só quero adicionar um aviso de que a enumeração de um grande buffer pool é muito lenta.
usr
1
Isso pressupõe que nenhuma página foi removida do buffer pool ainda.
Martin Smith
@MartinSmith As páginas podem ser despejadas antes do commit?
Solomon Rutzky,
5
@srutzky - sim. O log de transações possui todas as informações necessárias para reverter. Páginas sujas podem ser gravadas em disco - por exemplo, em um ponto de verificação ou pelo gravador Eager, especialmente nesse caso, e depois removidas do buffer pool.
Martin Smith
7

Para fins de monitoramento, gostaríamos de ter uma idéia aproximada do progresso dessa declaração enquanto ela estiver em execução.

Um fora ou em curso?

Se for necessário antecipar *, você pode usar sys.dm_exec_query_profiles

Conexão 1 (sessão 55)

SET STATISTICS XML ON

SELECT so1.*
INTO   dbo.TestDump
FROM   sys.all_objects so1
CROSS JOIN sys.all_objects so2
CROSS JOIN sys.all_objects so3
CROSS JOIN sys.all_objects so4
CROSS JOIN sys.all_objects so5;

Conexão 2

select row_count
from sys.dm_exec_query_profiles
WHERE physical_operator_name = 'Table Insert' 
    AND session_id = 55;

Pode ser necessário somar as contagens de linhas retornadas se SELECT INTOestiver usando paralelismo .

* A sessão que você deseja monitorar usando esta DMV deve estar ativada para coleta de estatísticas usando SET STATISTICS PROFILE ONou SET STATISTICS XML ON. A solicitação de um plano de execução "real" do SSMS também funciona (porque define a última opção).

Martin Smith
fonte
Parece que esqueci de marcar +1 em fevereiro, mas não esqueci completamente :). Acabei de usá-lo nesta questão relacionada, uma vez que o OP é pelo menos em 2014: dba.stackexchange.com/questions/139191/… Obrigado por apontar isso; é muito a calhar DMV :-)
Solomon Rutzky
2
@srutzky sim, é muito útil. E utilizado nos planos de execução ao vivo do SSMS 2016 msdn.microsoft.com/en-gb/library/dn831878.aspx
Martin Smith
5

Não acho que haja uma maneira de obter contagens de linhas, mas você pode estimar a quantidade de dados gravados observando:

SELECT writes 
  FROM sys.dm_exec_requests WHERE session_id = <x>;

SELECT COUNT(*) FROM sys.dm_db_database_page_allocations
(<dbid>, OBJECT_ID(N'dbo.newtablename'), 0, NULL, 'LIMITED');

Se você tem algum tipo de idéia de quantas páginas o heap deve ocupar ao terminar, você deve conseguir% de conclusão. A última consulta não será rápida à medida que a tabela aumentar. E provavelmente o mais seguro é executar o descrito acima READ UNCOMMITTED(e geralmente não é recomendável, para qualquer coisa).

Aaron Bertrand
fonte
4

Se você pudesse mudar o INSERTde

SELECT ... INTO DestTable FROM SrcTable

para um

INSERT DestTable SELECT ... FROM SrcTable

sua select count(*) from DestTable with (nolock)consulta funcionaria.

Se isso não for possível, você poderá usar sp_WhoIsActive (ou mergulhar nas DMVs) para monitorar quantas gravações a consulta faz. Esse seria um indicador bastante aproximado, mas poderia ser útil se você basear o número de gravações que normalmente realiza.

Você poderá obter um log mínimo com o INSERTdescrito acima, se adicionar WITH (TABLOCK).

James Anderson
fonte
Obrigado por este comentário. Queremos obter registro mínimo, que é por isso que estamos usando o SELECT ... INTO abordagem (e também porque nós somos tipo de preguiçoso ...)
Dan
1
Você deve conseguir obter o mínimo de log com os INSERTitens acima, se adicionar #WITH(TABLOCK)
James Anderson
@ JamesAnderson - Se a tabela for deixada como uma pilha, isso causará o bloqueio novamente, pois é necessário um BULK_OPERATIONbloqueio.
Martin Smith