¿Por qué una consulta SELECT causa escrituras?

34

Me di cuenta de que en un servidor que ejecuta SQL Server 2016 SP1 CU6 a veces una sesión de eventos extendidos muestra una consulta SELECT que causa escrituras. Por ejemplo:

ingrese la descripción de la imagen aquí

El plan de ejecución no muestra una causa obvia para las escrituras, como una tabla hash, un carrete o una clasificación que podría derramarse en TempDB:

ingrese la descripción de la imagen aquí

La asignación variable a un tipo MAX o una actualización automática de estadísticas también podría causar esto, pero tampoco fue la causa de las escrituras en este caso.

¿De qué más podrían ser las escrituras?

James L
fuente

Respuestas:

8

Torpe

No podía recordar si incluí estos en mi respuesta original , así que aquí hay otra pareja.

Carretes!

SQL Server tiene muchos spools diferentes, que son estructuras de datos temporales almacenadas en tempdb. Dos ejemplos son los carretes de tabla e índice.

Cuando ocurren en un plan de consulta, las escrituras en esos carretes se asociarán con la consulta.

NUECES

Estos también se registrarán como escrituras en DMV, profiler, XE, etc.

Carrete de índice

NUECES

Carrete de mesa

NUECES

La cantidad de escrituras realizadas aumentará con el tamaño de los datos en cola, obviamente.

Derrames

Cuando SQL Server no obtiene suficiente memoria para ciertos operadores, puede derramar algunas páginas al disco. Esto sucede principalmente con géneros y hashes. Puede ver esto en los planes de ejecución reales, y en las versiones más recientes del servidor SQL, los derrames también se rastrean en 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;

NUECES

NUECES

Rastreo

Puede usar una sesión XE similar a la que usé anteriormente para verlas en sus propias demostraciones.

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
fuente
38

En algunos casos, Query Store puede hacer que se produzcan escrituras como efecto de una instrucción select y en la misma sesión.

Esto se puede reproducir de la siguiente manera:

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';

Cree una sesión de eventos extendidos para monitorear:

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);

Luego ejecute lo siguiente:

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

Una transacción implícita puede o no ser necesaria para reproducir esto.

De forma predeterminada, en la parte superior de la próxima hora, el trabajo de recopilación de estadísticas del Almacén de consultas escribirá los datos. Esto parece ocurrir (¿a veces?) Como parte de la primera consulta de usuario ejecutada durante la hora. La sesión de eventos extendidos mostrará algo similar a lo siguiente:

ingrese la descripción de la imagen aquí

El registro de transacciones muestra las escrituras que se han producido:

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];

ingrese la descripción de la imagen aquí

La inspección de la página DBCC PAGEmuestra que las escrituras son para sys.plan_persist_runtime_stats_interval.

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

Tenga en cuenta que las entradas de registro muestran tres transacciones anidadas pero solo dos registros de confirmación. En una situación similar en la producción, esto condujo a una biblioteca de cliente posiblemente defectuosa que usó transacciones implícitas comenzando inesperadamente una transacción de escritura, evitando que el registro de transacciones se borre. La biblioteca se escribió para emitir solo una confirmación después de ejecutar una instrucción de actualización, inserción o eliminación, por lo que nunca emitió un comando de confirmación y dejó abierta una transacción de escritura.

James L
fuente
25

Hay otro momento en que esto puede suceder, y eso es con una actualización automática de estadísticas.

Aquí está la sesión 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

Luego usaremos esto para recopilar información:

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

Algunos de los resultados interesantes de la sesión XE:

NUECES

La actualización automática de estadísticas no muestra ninguna escritura, pero la consulta muestra una escritura inmediatamente después de la actualización de estadísticas.

Erik Darling
fuente