Por que uma consulta SELECT causaria gravações?

34

Percebi que, em um servidor executando o SQL Server 2016 SP1 CU6, às vezes uma sessão de Eventos Estendidos mostra uma consulta SELECT causando gravações. Por exemplo:

insira a descrição da imagem aqui

O plano de execução não mostra nenhuma causa óbvia para as gravações, como uma tabela de hash, um spool ou uma classificação que pode se espalhar para o TempDB:

insira a descrição da imagem aqui

A atribuição variável a um tipo MAX ou a uma atualização automática de estatísticas também pode causar isso, mas também não foi a causa das gravações neste caso.

De que mais poderiam ser as gravações?

James L
fonte

Respostas:

8

Desajeitado

Eu não conseguia me lembrar se os incluí na minha resposta original , então aqui está outro casal.

Carretéis!

O SQL Server possui muitos spools diferentes, que são estruturas de dados temporárias armazenadas no tempdb. Dois exemplos são os spools de tabela e índice.

Quando eles ocorrem em um plano de consulta, as gravações nesses spools serão associadas à consulta.

NUTS

Eles também serão registrados como gravações em DMVs, profiler, XE etc.

Spool de índice

NUTS

Carretel de mesa

NUTS

A quantidade de gravações executadas aumentará com o tamanho dos dados em spool, obviamente.

Derramamentos

Quando o SQL Server não obtém memória suficiente para determinados operadores, isso pode derramar algumas páginas no disco. Isso acontece principalmente com tipos e hashes. Você pode ver isso nos planos de execução reais e, nas versões mais recentes do SQL Server, os derramamentos também são rastreados em dm_exec_query_stats .

SELECT deqs.sql_handle,
       deqs.total_spills,
       deqs.last_spills,
       deqs.min_spills,
       deqs.max_spills
FROM sys.dm_exec_query_stats AS deqs
WHERE deqs.min_spills > 0;

NUTS

NUTS

Rastreamento

Você pode usar uma sessão XE semelhante à usada acima para vê-las em suas próprias demos.

CREATE EVENT SESSION spools_and_spills
    ON SERVER
    ADD EVENT sqlserver.sql_batch_completed
    ( ACTION ( sqlserver.sql_text ))
    ADD TARGET package0.event_file
    ( SET filename = N'c:\temp\spools_and_spills' )
    WITH ( MAX_MEMORY = 4096KB,
           EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
           MAX_DISPATCH_LATENCY = 1 SECONDS,
           MAX_EVENT_SIZE = 0KB,
           MEMORY_PARTITION_MODE = NONE,
           TRACK_CAUSALITY = OFF,
           STARTUP_STATE = OFF );
GO
Erik Darling
fonte
38

Em alguns casos, o Query Store pode causar gravações como efeito de uma instrução select e na mesma sessão.

Isso pode ser reproduzido da seguinte maneira:

USE master;
GO
CREATE DATABASE [Foo];
ALTER DATABASE [Foo] SET QUERY_STORE (OPERATION_MODE = READ_WRITE, 
  CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), 
  DATA_FLUSH_INTERVAL_SECONDS = 900, 
  INTERVAL_LENGTH_MINUTES = 60, 
  MAX_STORAGE_SIZE_MB = 100, 
  QUERY_CAPTURE_MODE = ALL, 
  SIZE_BASED_CLEANUP_MODE = AUTO);
USE Foo;
CREATE TABLE Test (a int, b nvarchar(max));
INSERT INTO Test SELECT 1, 'string';

Crie uma sessão de Eventos estendidos para monitoramento:

CREATE EVENT SESSION [Foo] ON SERVER 
ADD EVENT sqlserver.rpc_completed(SET collect_data_stream=(1)
    ACTION(sqlserver.client_app_name,sqlserver.client_hostname,sqlserver.client_pid,sqlserver.database_name,sqlserver.is_system,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_server_principal_name,sqlserver.sql_text)
    WHERE ([writes]>(0))),
ADD EVENT sqlserver.sql_batch_completed(SET collect_batch_text=(1)
    ACTION(sqlserver.client_app_name,sqlserver.client_hostname,sqlserver.client_pid,sqlserver.database_name,sqlserver.is_system,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_server_principal_name,sqlserver.sql_text)
    WHERE ([writes]>(0)))
ADD TARGET package0.event_file(SET filename=N'C:\temp\FooActivity2016.xel',max_file_size=(11),max_rollover_files=(999999))
WITH (MAX_MEMORY=32768 KB,EVENT_RETENTION_MODE=ALLOW_MULTIPLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF);

Em seguida, execute o seguinte:

WHILE @@TRANCOUNT > 0 COMMIT
SET IMPLICIT_TRANSACTIONS ON;
SET NOCOUNT ON;
GO
DECLARE @b nvarchar(max);
SELECT @b = b FROM dbo.Test WHERE a = 1;
WAITFOR DELAY '00:00:01.000';
GO 86400

Uma transação implícita pode ou não ser necessária para reproduzir isso.

Por padrão, na parte superior da próxima hora, o trabalho de coleta de estatísticas do Query Store gravará os dados. Parece que (às vezes?) Ocorre como parte da primeira consulta do usuário executada durante a hora. A sessão Eventos Estendidos mostrará algo semelhante ao seguinte:

insira a descrição da imagem aqui

O log de transações mostra as gravações que ocorreram:

USE Foo;
SELECT [Transaction ID], [Begin Time], SPID, Operation, 
  [Description], [Page ID], [Slot ID], [Parent Transaction ID] 
FROM sys.fn_dblog(null,null) 
/* Adjust based on contents of your transaction log */
WHERE [Transaction ID] IN ('0000:0000042c', '0000:0000042d', '0000:0000042e')
OR [Parent Transaction ID] IN ('0000:0000042c', '0000:0000042d', '0000:0000042e')
ORDER BY [Current LSN];

insira a descrição da imagem aqui

Inspecionar a página DBCC PAGEmostra que as gravações são para sys.plan_persist_runtime_stats_interval.

USE Foo;
DBCC TRACEON(3604); 
DBCC PAGE(5,1,344,1); SELECT
OBJECT_NAME(229575856);

Observe que as entradas do log mostram três transações aninhadas, mas apenas dois registros de confirmação. Em uma situação semelhante na produção, isso levou a uma biblioteca-cliente com falha indiscutível que usava transações implícitas inesperadamente iniciando uma transação de gravação, impedindo a limpeza do log de transações. A biblioteca foi gravada para emitir apenas uma confirmação após executar uma instrução de atualização, inserção ou exclusão, portanto, nunca emitiu um comando de confirmação e deixou uma transação de gravação aberta.

James L
fonte
25

Há outro momento em que isso pode acontecer, e é com uma atualização automática de estatísticas.

Aqui está a sessão XE que veremos:

CREATE EVENT SESSION batches_and_stats
    ON SERVER
    ADD EVENT sqlserver.auto_stats
    ( ACTION ( sqlserver.sql_text )),
    ADD EVENT sqlserver.sql_batch_completed
    ( ACTION ( sqlserver.sql_text ))
    ADD TARGET package0.event_file
    ( SET filename = N'c:\temp\batches_and_stats' )
    WITH ( MAX_MEMORY = 4096KB,
           EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
           MAX_DISPATCH_LATENCY = 30 SECONDS,
           MAX_EVENT_SIZE = 0KB,
           MEMORY_PARTITION_MODE = NONE,
           TRACK_CAUSALITY = OFF,
           STARTUP_STATE = OFF );
GO

Em seguida, usaremos isso para coletar informações:

USE tempdb

DROP TABLE IF EXISTS dbo.SkewedUp

CREATE TABLE dbo.SkewedUp (Id INT NOT NULL, INDEX cx_su CLUSTERED (Id))

INSERT dbo.SkewedUp WITH ( TABLOCK ) ( Id )
SELECT CASE WHEN x.r % 15 = 0 THEN 1
            WHEN x.r % 5 = 0 THEN 1000
            WHEN x.r % 3 = 0 THEN 10000
            ELSE 100000
       END AS Id
FROM   (   SELECT     TOP 1000000 ROW_NUMBER() OVER ( ORDER BY @@DBTS ) AS r
           FROM       sys.messages AS m
           CROSS JOIN sys.messages AS m2 ) AS x;


ALTER EVENT SESSION [batches_and_stats] ON SERVER STATE = START

SELECT su.Id, COUNT(*) AS records
FROM dbo.SkewedUp AS su
WHERE su.Id > 0
GROUP BY su.Id

ALTER EVENT SESSION [batches_and_stats] ON SERVER STATE = STOP

Alguns dos resultados interessantes da sessão XE:

NUTS

A atualização automática de estatísticas não mostra gravações, mas a consulta mostra uma gravação imediatamente após a atualização das estatísticas.

Erik Darling
fonte