MSSQL query store: differenze tra le versioni

Da Emigar.
Vai alla navigazione Vai alla ricerca
Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
 
(6 versioni intermedie di uno stesso utente non sono mostrate)
Riga 1: Riga 1:
=Attivare e Disattivare=
==attivare query store==
ALTER DATABASE [NomeTuoDatabase]
SET QUERY_STORE = ON (
OPERATION_MODE = READ_WRITE, -- Attiva la raccolta dati
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), -- Conserva i dati per 30 giorni (regolabile)
INTERVAL_LENGTH_MINUTES = 60, -- Intervallo di aggregazione (es. 1 ora)
MAX_STORAGE_SIZE_MB = 1000, -- Spazio massimo occupato dal Query Store (es. 1 GB)
QUERY_CAPTURE_MODE = AUTO -- Modalità di cattura (cattura le query rilevanti evitando il rumore)
);



==disattiva query store==
ALTER DATABASE [NomeTuoDatabase]
SET QUERY_STORE = ON (
OPERATION_MODE = READ_ONLY, -- Impedisce qualsiasi scrittura/cancellazione
QUERY_CAPTURE_MODE = NONE, -- Disattiva la cattura di nuove query
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 0), -- 0 = Disabilita la pulizia automatica per età
SIZE_BASED_CLEANUP_MODE = OFF -- Disabilita la pulizia al raggiungimento del limite di spazio
);

o più drastico:

ALTER DATABASE [NomeTuoDatabase] SET QUERY_STORE = OFF;

==interrogare query store==

Verifica se attivo.
SELECT
desired_state_desc,
actual_state_desc,
readonly_reason,
current_storage_size_mb,
max_storage_size_mb
FROM sys.database_query_store_options;

=Analisi=
==analizzare query==
<pre>
<pre>
SELECT
SELECT
Riga 21: Riga 58:
</pre>
</pre>


==query onerose per risorse==
<pre>
SELECT TOP 50
q.query_id,
p.plan_id,
qt.query_sql_text,
SUM(rs.count_executions) AS total_executions,
CAST(AVG(rs.avg_logical_io_reads) AS BIGINT) AS avg_logical_reads,
SUM(CAST(rs.avg_logical_io_reads AS BIGINT) * rs.count_executions) AS total_logical_reads,
CAST(AVG(rs.avg_duration / 1000.0) AS DECIMAL(38, 2)) AS avg_duration_ms
FROM sys.query_store_query AS q
INNER JOIN sys.query_store_query_text AS qt
ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_plan AS p
ON q.query_id = p.query_id
INNER JOIN sys.query_store_runtime_stats AS rs
ON p.plan_id = rs.plan_id
GROUP BY
q.query_id,
p.plan_id,
qt.query_sql_text
ORDER BY
avg_logical_reads DESC;
</pre>

==query con full scan onerose==

<pre>
SET NOCOUNT ON;

-- Tabella temporanea per raccogliere i risultati
IF OBJECT_ID('tempdb..#ScanResults') IS NOT NULL DROP TABLE #ScanResults;

CREATE TABLE #ScanResults (
query_id BIGINT,
plan_id BIGINT,
query_sql_text NVARCHAR(MAX),
scan_type VARCHAR(50),
scan_cost_percent DECIMAL(5,2),
avg_duration_ms DECIMAL(38,2),
avg_logical_io_reads BIGINT,
total_executions BIGINT
);

-- Step 1: Estrazione rapida dei soli 50 piani a più alto I/O
IF OBJECT_ID('tempdb..#TopPlans') IS NOT NULL DROP TABLE #TopPlans;

SELECT TOP 50
p.plan_id,
p.query_id,
qt.query_sql_text,
p.query_plan,
SUM(rs.count_executions) AS total_executions,
CAST(AVG(rs.avg_duration / 1000.0) AS DECIMAL(38,2)) AS avg_duration_ms,
CAST(AVG(rs.avg_logical_io_reads) AS BIGINT) AS avg_logical_io_reads
INTO #TopPlans
FROM sys.query_store_plan p
INNER JOIN sys.query_store_query q ON p.query_id = q.query_id
INNER JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
WHERE p.query_plan LIKE '%PhysicalOp="Table Scan"%'
OR p.query_plan LIKE '%PhysicalOp="Clustered Index Scan"%'
OR p.query_plan LIKE '%PhysicalOp="Index Scan"%'
GROUP BY p.plan_id, p.query_id, qt.query_sql_text, p.query_plan
ORDER BY avg_logical_io_reads DESC;

-- Step 2: Ciclo protetto sui 50 piani per gestire gli errori XML individualmente
DECLARE @plan_id BIGINT, @query_id BIGINT, @sql_text NVARCHAR(MAX), @plan_xml_str NVARCHAR(MAX);
DECLARE @avg_dur DECIMAL(38,2), @avg_io BIGINT, @tot_exec BIGINT;

DECLARE plan_cursor CURSOR LOCAL FAST_FORWARD FOR
SELECT plan_id, query_id, query_sql_text, query_plan, avg_duration_ms, avg_logical_io_reads, total_executions
FROM #TopPlans;

OPEN plan_cursor;
FETCH NEXT FROM plan_cursor INTO @plan_id, @query_id, @sql_text, @plan_xml_str, @avg_dur, @avg_io, @tot_exec;

WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
-- Esegue il parsing XML preciso per il singolo piano
WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
INSERT INTO #ScanResults
SELECT
@query_id,
@plan_id,
@sql_text,
scan_nodes.scan_op.value('@PhysicalOp', 'VARCHAR(50)') AS scan_type,
CAST((scan_nodes.scan_op.value('@EstimatedTotalSubtreeCost', 'FLOAT') / NULLIF(stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT'), 0)) * 100 AS DECIMAL(5,2)) AS scan_cost_percent,
@avg_dur,
@avg_io,
@tot_exec
FROM (SELECT CAST(@plan_xml_str AS XML) AS xPlan) AS xml_plan
CROSS APPLY xml_plan.xPlan.nodes('//StmtSimple') AS stmt(stmt_xml)
CROSS APPLY stmt.stmt_xml.nodes('.//RelOp[@PhysicalOp="Table Scan" or @PhysicalOp="Clustered Index Scan" or @PhysicalOp="Index Scan"]') AS scan_nodes(scan_op)
WHERE stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT') > 0
AND (scan_nodes.scan_op.value('@EstimatedTotalSubtreeCost', 'FLOAT') / stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT')) >= 0.50;
END TRY
BEGIN CATCH
-- In caso di errore XML (>128 livelli di annidamento), ignora solo questo piano e prosegue
END CATCH;

FETCH NEXT FROM plan_cursor INTO @plan_id, @query_id, @sql_text, @plan_xml_str, @avg_dur, @avg_io, @tot_exec;
END

CLOSE plan_cursor;
DEALLOCATE plan_cursor;

-- Step 3: Estrazione del risultato finale
SELECT
query_id,
plan_id,
query_sql_text,
scan_type,
scan_cost_percent,
avg_duration_ms,
avg_logical_io_reads,
total_executions
FROM #ScanResults
ORDER BY avg_logical_io_reads DESC;
</pre>


[[Categoria:Database]]
[[Categoria:Database]]

Versione attuale delle 13:44, 18 set 2026

Attivare e Disattivare

attivare query store

ALTER DATABASE [NomeTuoDatabase] 
SET QUERY_STORE = ON (
   OPERATION_MODE = READ_WRITE,                -- Attiva la raccolta dati
   CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 30), -- Conserva i dati per 30 giorni (regolabile)
   INTERVAL_LENGTH_MINUTES = 60,               -- Intervallo di aggregazione (es. 1 ora)
   MAX_STORAGE_SIZE_MB = 1000,                 -- Spazio massimo occupato dal Query Store (es. 1 GB)
   QUERY_CAPTURE_MODE = AUTO                   -- Modalità di cattura (cattura le query rilevanti evitando il rumore)
);


disattiva query store

ALTER DATABASE [NomeTuoDatabase]
SET QUERY_STORE = ON (
   OPERATION_MODE = READ_ONLY,                         -- Impedisce qualsiasi scrittura/cancellazione
   QUERY_CAPTURE_MODE = NONE,                          -- Disattiva la cattura di nuove query
   CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 0), -- 0 = Disabilita la pulizia automatica per età
   SIZE_BASED_CLEANUP_MODE = OFF                       -- Disabilita la pulizia al raggiungimento del limite di spazio
);

o più drastico:

ALTER DATABASE [NomeTuoDatabase] SET QUERY_STORE = OFF;

interrogare query store

Verifica se attivo.

SELECT 
   desired_state_desc, 
   actual_state_desc, 
   readonly_reason, 
   current_storage_size_mb, 
   max_storage_size_mb
FROM sys.database_query_store_options;

Analisi

analizzare query

SELECT
    qt.query_sql_text,
    rs.avg_duration,
    rs.count_executions,
    rs.avg_cpu_time,
    rsi.start_time,
    rsi.end_time
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi
    ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
JOIN sys.query_store_plan p
    ON rs.plan_id = p.plan_id
JOIN sys.query_store_query q
    ON p.query_id = q.query_id
JOIN sys.query_store_query_text qt
    ON q.query_text_id = qt.query_text_id
WHERE rsi.start_time >= '2026-06-11 14:00'
  AND rsi.end_time   <= '2026-06-11 16:00';

query onerose per risorse

SELECT TOP 50
    q.query_id,
    p.plan_id,
    qt.query_sql_text,
    SUM(rs.count_executions) AS total_executions,
    CAST(AVG(rs.avg_logical_io_reads) AS BIGINT) AS avg_logical_reads,
    SUM(CAST(rs.avg_logical_io_reads AS BIGINT) * rs.count_executions) AS total_logical_reads,
    CAST(AVG(rs.avg_duration / 1000.0) AS DECIMAL(38, 2)) AS avg_duration_ms
FROM sys.query_store_query AS q
INNER JOIN sys.query_store_query_text AS qt 
    ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_plan AS p 
    ON q.query_id = p.query_id
INNER JOIN sys.query_store_runtime_stats AS rs 
    ON p.plan_id = rs.plan_id
GROUP BY 
    q.query_id,
    p.plan_id,
    qt.query_sql_text
ORDER BY 
    avg_logical_reads DESC;

query con full scan onerose

SET NOCOUNT ON;

-- Tabella temporanea per raccogliere i risultati
IF OBJECT_ID('tempdb..#ScanResults') IS NOT NULL DROP TABLE #ScanResults;

CREATE TABLE #ScanResults (
    query_id BIGINT,
    plan_id BIGINT,
    query_sql_text NVARCHAR(MAX),
    scan_type VARCHAR(50),
    scan_cost_percent DECIMAL(5,2),
    avg_duration_ms DECIMAL(38,2),
    avg_logical_io_reads BIGINT,
    total_executions BIGINT
);

-- Step 1: Estrazione rapida dei soli 50 piani a più alto I/O
IF OBJECT_ID('tempdb..#TopPlans') IS NOT NULL DROP TABLE #TopPlans;

SELECT TOP 50
    p.plan_id,
    p.query_id,
    qt.query_sql_text,
    p.query_plan,
    SUM(rs.count_executions) AS total_executions,
    CAST(AVG(rs.avg_duration / 1000.0) AS DECIMAL(38,2)) AS avg_duration_ms,
    CAST(AVG(rs.avg_logical_io_reads) AS BIGINT) AS avg_logical_io_reads
INTO #TopPlans
FROM sys.query_store_plan p
INNER JOIN sys.query_store_query q ON p.query_id = q.query_id
INNER JOIN sys.query_store_query_text qt ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
WHERE p.query_plan LIKE '%PhysicalOp="Table Scan"%'
   OR p.query_plan LIKE '%PhysicalOp="Clustered Index Scan"%'
   OR p.query_plan LIKE '%PhysicalOp="Index Scan"%'
GROUP BY p.plan_id, p.query_id, qt.query_sql_text, p.query_plan
ORDER BY avg_logical_io_reads DESC;

-- Step 2: Ciclo protetto sui 50 piani per gestire gli errori XML individualmente
DECLARE @plan_id BIGINT, @query_id BIGINT, @sql_text NVARCHAR(MAX), @plan_xml_str NVARCHAR(MAX);
DECLARE @avg_dur DECIMAL(38,2), @avg_io BIGINT, @tot_exec BIGINT;

DECLARE plan_cursor CURSOR LOCAL FAST_FORWARD FOR
SELECT plan_id, query_id, query_sql_text, query_plan, avg_duration_ms, avg_logical_io_reads, total_executions
FROM #TopPlans;

OPEN plan_cursor;
FETCH NEXT FROM plan_cursor INTO @plan_id, @query_id, @sql_text, @plan_xml_str, @avg_dur, @avg_io, @tot_exec;

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        -- Esegue il parsing XML preciso per il singolo piano
        WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
        INSERT INTO #ScanResults
        SELECT 
            @query_id,
            @plan_id,
            @sql_text,
            scan_nodes.scan_op.value('@PhysicalOp', 'VARCHAR(50)') AS scan_type,
            CAST((scan_nodes.scan_op.value('@EstimatedTotalSubtreeCost', 'FLOAT') / NULLIF(stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT'), 0)) * 100 AS DECIMAL(5,2)) AS scan_cost_percent,
            @avg_dur,
            @avg_io,
            @tot_exec
        FROM (SELECT CAST(@plan_xml_str AS XML) AS xPlan) AS xml_plan
        CROSS APPLY xml_plan.xPlan.nodes('//StmtSimple') AS stmt(stmt_xml)
        CROSS APPLY stmt.stmt_xml.nodes('.//RelOp[@PhysicalOp="Table Scan" or @PhysicalOp="Clustered Index Scan" or @PhysicalOp="Index Scan"]') AS scan_nodes(scan_op)
        WHERE stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT') > 0
          AND (scan_nodes.scan_op.value('@EstimatedTotalSubtreeCost', 'FLOAT') / stmt.stmt_xml.value('@StatementSubTreeCost', 'FLOAT')) >= 0.50;
    END TRY
    BEGIN CATCH
        -- In caso di errore XML (>128 livelli di annidamento), ignora solo questo piano e prosegue
    END CATCH;

    FETCH NEXT FROM plan_cursor INTO @plan_id, @query_id, @sql_text, @plan_xml_str, @avg_dur, @avg_io, @tot_exec;
END

CLOSE plan_cursor;
DEALLOCATE plan_cursor;

-- Step 3: Estrazione del risultato finale
SELECT 
    query_id,
    plan_id,
    query_sql_text,
    scan_type,
    scan_cost_percent,
    avg_duration_ms,
    avg_logical_io_reads,
    total_executions
FROM #ScanResults
ORDER BY avg_logical_io_reads DESC;