Mengapa kueri SELECT menyebabkan penulisan?

34

Saya perhatikan bahwa pada server yang menjalankan SQL Server 2016 SP1 CU6 kadang-kadang sesi Peristiwa Diperpanjang menunjukkan kueri SELECT yang menyebabkan penulisan. Sebagai contoh:

masukkan deskripsi gambar di sini

Rencana eksekusi tidak menunjukkan alasan yang jelas untuk penulisan, seperti tabel hash, spool, atau sortir yang bisa tumpah ke TempDB:

masukkan deskripsi gambar di sini

Penugasan variabel ke tipe MAX atau pembaruan statistik otomatis juga dapat menyebabkan hal ini, tetapi juga bukan penyebab penulisan dalam kasus ini.

Dari mana lagi yang bisa menulis?

James L
sumber

Jawaban:

8

Ceroboh

Saya tidak ingat apakah saya memasukkan ini dalam jawaban asli saya , jadi inilah pasangan lain.

Kelos!

SQL Server memiliki banyak gulungan yang berbeda, yang merupakan struktur data sementara disimpan di tempdb. Dua contoh adalah gulungan Tabel dan Indeks.

Ketika mereka muncul dalam rencana kueri, penulisan ke gulungan tersebut akan dikaitkan dengan kueri.

GILA

Ini juga akan didaftarkan sebagai tulisan dalam DMV, profiler, XE, dll.

Kumparan Indeks

GILA

Spool Meja

GILA

Jumlah penulisan yang dilakukan akan naik dengan ukuran data spooled, jelas.

Tumpahan

Ketika SQL Server tidak mendapatkan cukup memori untuk operator tertentu, itu mungkin menumpahkan beberapa halaman ke disk. Ini terutama terjadi dengan macam dan hash. Anda dapat melihat ini dalam rencana eksekusi aktual, dan dalam versi SQL server yang lebih baru, tumpahan juga dilacak di 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;

GILA

GILA

Pelacakan

Anda dapat menggunakan sesi XE serupa dengan yang saya gunakan di atas untuk melihat ini dalam demo Anda sendiri.

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

Dalam beberapa kasus, Query Store dapat menyebabkan penulisan terjadi sebagai efek dari pernyataan pilih, dan dalam sesi yang sama.

Ini dapat direproduksi sebagai berikut:

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

Buat sesi Acara yang Diperpanjang untuk pemantauan:

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

Selanjutnya jalankan yang berikut:

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

Transaksi implisit mungkin atau mungkin tidak perlu untuk mereproduksi ini.

Secara default, di bagian atas jam berikutnya pekerjaan pengumpulan statistik Query Store akan menulis data. Ini tampaknya (kadang-kadang?) Terjadi sebagai bagian dari kueri pengguna pertama yang dieksekusi selama satu jam. Sesi Acara Diperpanjang akan menampilkan sesuatu yang mirip dengan yang berikut:

masukkan deskripsi gambar di sini

Log transaksi menunjukkan penulisan yang telah terjadi:

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

masukkan deskripsi gambar di sini

Memeriksa halaman dengan DBCC PAGEmenunjukkan bahwa menulis adalah untuk sys.plan_persist_runtime_stats_interval.

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

Perhatikan bahwa entri log menunjukkan tiga transaksi bersarang tetapi hanya dua catatan komit. Dalam situasi yang sama dalam produksi, ini menyebabkan perpustakaan klien yang bisa dibilang salah yang menggunakan transaksi implisit secara tak terduga memulai transaksi tulis, mencegah log transaksi dari kliring. Perpustakaan ditulis untuk hanya mengeluarkan komit setelah menjalankan pembaruan, menyisipkan, atau menghapus pernyataan, sehingga tidak pernah mengeluarkan perintah komit dan membiarkan transaksi tulis terbuka.

James L
sumber
25

Ada waktu lain ketika ini mungkin terjadi, dan itu dengan pembaruan statistik otomatis.

Inilah sesi XE yang akan kita bahas:

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

Maka kami akan menggunakan ini untuk mengumpulkan informasi:

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

Beberapa hasil menarik dari Sesi XE:

GILA

Pembaruan statistik otomatis tidak menunjukkan penulisan apa pun, tetapi kueri menunjukkan satu penulisan segera setelah pembaruan statistik.

Erik Darling
sumber