Warum würde eine SELECT-Abfrage Schreibvorgänge verursachen?

34

Ich habe festgestellt, dass auf einem Server mit SQL Server 2016 SP1 CU6 in einer Sitzung mit erweiterten Ereignissen manchmal eine SELECT-Abfrage angezeigt wird, die Schreibvorgänge verursacht. Beispielsweise:

Bildbeschreibung hier eingeben

Der Ausführungsplan zeigt keine offensichtliche Ursache für die Schreibvorgänge an, wie z. B. eine Hash-Tabelle, ein Spool oder eine Sortierung, die in TempDB übertragen werden könnten:

Bildbeschreibung hier eingeben

Die Zuweisung einer Variablen zu einem MAX-Typ oder eine automatische Statistikaktualisierung können ebenfalls dazu führen, in diesem Fall jedoch auch nicht die Ursache für die Schreibvorgänge.

Woraus könnten die Schriften noch stammen?

James L
quelle

Antworten:

8

Ungeschickt

Ich konnte mich nicht erinnern, ob ich diese in meine ursprüngliche Antwort aufgenommen hatte. Hier ist ein weiteres Paar.

Spulen!

SQL Server verfügt über viele verschiedene Spools, bei denen es sich um temporäre Datenstrukturen handelt, die in Tempdb gespeichert sind. Zwei Beispiele sind Table- und Index-Spools.

Wenn sie in einem Abfrageplan auftreten, werden die Schreibvorgänge in diese Spools mit der Abfrage verknüpft.

NÜSSE

Diese werden auch als Schreibzugriff in DMVs, Profiler, XE usw. registriert.

Index-Spool

NÜSSE

Tischspule

NÜSSE

Die Anzahl der ausgeführten Schreibvorgänge hängt natürlich von der Größe der gespoolten Daten ab.

Verschüttungen

Wenn SQL Server für bestimmte Operatoren nicht genügend Arbeitsspeicher zur Verfügung stellt, werden möglicherweise einige Seiten auf die Festplatte verschoben. Dies geschieht hauptsächlich mit Sorten und Hashes. Sie können dies in tatsächlichen Ausführungsplänen sehen, und in neueren Versionen von SQL Server werden Verschüttungen auch in dm_exec_query_stats nachverfolgt .

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;

NÜSSE

NÜSSE

Verfolgung

Sie können eine ähnliche XE-Sitzung wie die oben beschriebene verwenden, um diese in Ihren eigenen Demos anzuzeigen.

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

In einigen Fällen kann der Abfragespeicher Schreibvorgänge als Auswirkung einer select-Anweisung und in derselben Sitzung verursachen.

Dies kann wie folgt reproduziert werden:

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

Erstellen Sie eine erweiterte Ereignissitzung zur Überwachung:

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

Führen Sie als Nächstes Folgendes aus:

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

Eine implizite Transaktion kann erforderlich sein oder auch nicht, um dies zu reproduzieren.

Standardmäßig schreibt der Statistiksammlungsjob von Query Store in der nächsten Stunde Daten aus. Dies scheint (manchmal?) Im Rahmen der ersten Benutzerabfrage zu geschehen, die innerhalb einer Stunde ausgeführt wird. In der Sitzung für erweiterte Ereignisse wird Folgendes angezeigt:

Bildbeschreibung hier eingeben

Das Transaktionsprotokoll zeigt die aufgetretenen Schreibvorgänge an:

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

Bildbeschreibung hier eingeben

Das Betrachten der Seite mit DBCC PAGEzeigt, dass die Schreibvorgänge ausgeführt werden sollen sys.plan_persist_runtime_stats_interval.

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

Beachten Sie, dass in den Protokolleinträgen drei verschachtelte Transaktionen, aber nur zwei Festschreibungsdatensätze angezeigt werden. In einer ähnlichen Situation in der Produktion führte dies zu einer möglicherweise fehlerhaften Clientbibliothek, die implizite Transaktionen verwendete, die unerwartet eine Schreibtransaktion starteten, wodurch das Löschen des Transaktionsprotokolls verhindert wurde. Die Bibliothek wurde so geschrieben, dass sie nur nach dem Ausführen einer Aktualisierungs-, Einfüge- oder Löschanweisung ein Commit ausführt. Sie hat also nie einen Commit-Befehl ausgegeben und eine Schreibtransaktion offen gelassen.

James L
quelle
25

Es gibt eine andere Zeit, in der dies passieren kann, und zwar mit einer automatischen Aktualisierung der Statistiken.

Hier ist die XE-Sitzung, die wir uns ansehen werden:

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

Dann werden wir dies verwenden, um Informationen zu sammeln:

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

Einige der interessanten Ergebnisse der XE-Sitzung:

NÜSSE

Das automatische Statistik-Update zeigt keine Schreibvorgänge an, die Abfrage zeigt jedoch einen Schreibvorgang unmittelbar nach dem Statistik-Update an.

Erik Darling
quelle