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:
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:
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?
Je ne me souvenais pas si je les avais inclus dans ma réponse d'origine , alors voici un autre couple.
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.
Celles-ci seront également enregistrées comme écritures dans DMV, profiler, XE, etc.
Le nombre d'écritures effectuées augmentera évidemment avec la taille des données spoulées.
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;
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
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:
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];
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.
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:
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.