web-dev-qa-db-fra.com

Pourquoi une requête SELECT provoquerait-elle des écritures?

J'ai remarqué que sur un serveur exécutant SQL Server 2016 SP1 CU6, une session d'événements étendus affiche parfois une requête SELECT provoquant des écritures. Par exemple:

enter image description here

Le plan d'exécution ne montre aucune cause évidente pour les écritures, comme une table de hachage, un spouleur ou un tri qui pourrait se répandre sur TempDB:

enter image description here

L'affectation de variables à un type MAX ou une mise à jour automatique des statistiques pourrait également provoquer cela, mais ce n'était pas non plus la cause des écritures dans ce cas.

De quoi d'autre les écritures pourraient-elles provenir?

34
James L

Maladroit

Je ne me souvenais pas si je les avais inclus dans ma réponse d'origine , alors voici un autre couple.

Bobines!

SQL Server a beaucoup de spools différents, qui sont des structures de données temporaires stockées dans tempdb. Deux exemples sont les bobines de table et d'index.

Lorsqu'elles se produisent dans un plan de requête, les écritures sur ces spools seront associées à la requête.

NUTS

Celles-ci seront également enregistrées comme écritures dans DMV, profiler, XE, etc.

Index Spool

NUTS

Bobine de table

NUTS

Le nombre d'écritures effectuées augmentera évidemment avec la taille des données spoulées.

Déversements

Lorsque SQL Server n'obtient pas suffisamment de mémoire pour certains opérateurs, il peut renverser certaines pages sur le disque. Cela se produit principalement avec les tris et les hachages. Vous pouvez le voir dans les plans d'exécution réels et dans les versions plus récentes de SQL Server, les déversements sont également suivis dans 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

Suivi

Vous pouvez utiliser une session XE similaire à celle que j'ai utilisée ci-dessus pour les voir dans vos propres démos.

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
8
Erik Darling

Dans certains cas, le magasin de requêtes peut provoquer des écritures en tant qu'effet d'une instruction select et dans la même session.

Cela peut être reproduit comme suit:

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

Créez une session d'événements étendus pour la surveillance:

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

Exécutez ensuite ce qui suit:

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

Une transaction implicite peut être nécessaire ou non pour la reproduire.

Par défaut, en haut de la prochaine heure, le travail de collecte de statistiques du magasin de requêtes écrit les données. Cela semble (parfois?) Se produire dans le cadre de la première requête utilisateur exécutée pendant l'heure. La session Événements étendus affichera quelque chose de semblable au suivant:

enter image description here

Le journal des transactions affiche les écritures qui ont eu lieu:

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

enter image description here

Inspection de la page avec DBCC PAGE montre que les écritures sont à sys.plan_persist_runtime_stats_interval.

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

Notez que les entrées de journal affichent trois transactions imbriquées mais seulement deux enregistrements de validation. Dans une situation similaire en production, cela a conduit à une bibliothèque client sans doute défectueuse qui a utilisé des transactions implicites pour démarrer de manière inattendue une transaction d'écriture, empêchant le journal des transactions de s'effacer. La bibliothèque a été écrite pour émettre un commit uniquement après l'exécution d'une instruction update, insert ou delete, elle n'a donc jamais émis de commande commit et laissé une transaction d'écriture ouverte.

38
James L

Il y a un autre moment où cela peut arriver, et c'est avec une mise à jour automatique des statistiques.

Voici la session XE que nous examinerons:

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

Ensuite, nous l'utiliserons pour collecter des informations:

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

Quelques résultats intéressants de la session XE:

NUTS

La mise à jour automatique des statistiques n'affiche aucune écriture, mais la requête affiche une écriture immédiatement après la mise à jour des statistiques.

25
Erik Darling