2017-09-13 6 views
1

Dans SQL Server, j'essaie de créer une seule requête qui saisit une ligne et inclut les données agrégées d'une fenêtre de deux heures avant cette ligne ainsi que les données agrégées d'une ligne. fenêtre de l'heure après. Comment puis-je faire cela plus vite?Accélération de SQL Server pour appliquer des données agrégées

Les lignes sont horodatées avec une précision d'une milliseconde et ne sont pas espacées régulièrement. J'ai plus de 50 millions lignes dans ce tableau, et la requête ne semble pas être complète. Il y a des index dans beaucoup d'endroits, mais ils ne semblent pas aider. Je pensais aussi à utiliser une fonction de fenêtre, mais je ne suis pas sûr qu'il soit possible d'avoir une fenêtre coulissante avec des rangées inégalement distribuées. Aussi, pour la future fenêtre d'une heure, je ne suis pas sûr de savoir comment cela serait fait avec une fenêtre SQL.

Box est une chaîne de 10 valeurs uniques. Processus est une chaîne et a 30 valeurs uniques. La durée moyenne_ms est de 200 ms. Les erreurs représentent moins de 0,1% des données. Les 50 millions de lignes correspondent à des années de données.

select 
c1.start_time, 
c1.end_time, 
c1.box, 
c1.process, 
datediff(ms,c1.start_time,c1.end_time) as duration_ms, 
datepart(dw,c1.start_time) as day_of_week, 
datepart(hour,c1.start_time) as hour_of_day, 
c3.*, 
c5.* 
from metrics_table c1 
cross apply 
(select 
    avg(cast(datediff(ms,c2.start_time,c2.end_time) as numeric)) as avg_ms, 
    count(1) as num_process_total, 
    count(distinct process) as num_process_unique, 
    count(distinct box) as num_box_unique 
    from metrics_table c2 
    where datediff(minute,c2.start_time,c1.start_time) <= 120 
    and c1.start_time> c2.start_time 
    and c2.error_code = 0 
) c3 
cross apply 
(select 
    avg(case when datediff(ms,c4.start_time,c4.end_time)>1000 then 1.0 else 0.0 end) as percent_over_thresh 
    from metrics_table c4 
    where datediff(hour,c1.start_time,c4.start_time) <= 1 
    and c4.start_time> c1.start_time 
    and c4.error_code= 0 
) c5 
where 
c1.error_code= 0 

Modifier

Version: SQL Azure 12.0

Ajout plan d'exécution: enter image description here

+5

Je serais surpris si le problème de performance n'est pas à cause de vos prédicats où. Vous avez des fonctions dans votre clause where, ce qui signifie que vous devez calculer datediff pour chaque ligne. Et dans ce cas, vous le faites deux fois. Cela signifie que vous effectuez environ 100 millions de calculs de datiff. –

+1

@Hogan J'ai essayé de faire du fenêtrage, mais je n'ai pas vu une approche qui me permettrait d'aller -2 heures à partir d'un moment si les points de données ne sont pas collectés à intervalles réguliers. Cela signifie que la différence d'une ligne à l'autre pourrait être de quelques millisecondes, pourrait être quelques secondes, pourrait être des minutes – user4446237

+0

Yep ce n'est pas possible dans l'implémentation de SQL Server (pas de "RANGE ENTRE INTERVALLE") vous auriez à faire quelques pré-agrégation pour garantir une ligne par minute etc. Mais 'COUNT (DISTINCT ...)' n'est pas facilement compatible avec cela. –

Répondre

3

Ce qui suit devrait être un pas dans la bonne direction ... Note: c2.start_time & c4.start_time ne sont plus encapsulés dans les fonctions DATEDIFF ce qui les rend SARGable ...

SELECT 
    c1.start_time, 
    c1.end_time, 
    c1.box, 
    c1.process, 
    DATEDIFF(ms, c1.start_time, c1.end_time) AS duration_ms, 
    DATEPART(dw, c1.start_time) AS day_of_week, 
    DATEPART(HOUR, c1.start_time) AS hour_of_day, 
    --c3.*, 
    avg_ms = CASE WHEN 
    c5.* 
FROM 
    dbo.metrics_table c1 
    CROSS APPLY (
       SELECT 
        AVG(CAST(DATEDIFF(ms, c2.start_time, c2.end_time) AS NUMERIC)) AS avg_ms, 
        COUNT(1) AS num_process_total, 
        COUNT(DISTINCT process) AS num_process_unique, 
        COUNT(DISTINCT box) AS num_box_unique 
       FROM 
        dbo.metrics_table c2 
       WHERE 
        --DATEDIFF(minute,c2.start_time,c1.start_time) <= 120 
        c2.start_time <= DATEADD(MINUTE, -120, c1.start_time) 
        --and c1.start_time> c2.start_time 
        AND c2.error_code = 0 
       ) c3 
    CROSS APPLY (
       SELECT 
        AVG(CASE WHEN DATEDIFF(ms, c4.start_time, c4.end_time) > 1000 THEN 1.0 ELSE 0.0 END 
        ) AS percent_over_thresh 
       FROM 
        dbo.metrics_table c4 
       WHERE 
        --DATEDIFF(HOUR, c1.start_time, c4.start_time) <= 1 
        c4.start_time >= DATEADD(HOUR, 1, c1.start_time) 
        --and c4.start_time> c1.start_time 
        AND c4.error_code = 0 
       ) c5 
WHERE 
    c1.error_code = 0; 

Bien sûr, faire une requête SARGable ne sert à rien sauf si un index approprié est disponible. Ce qui suit devrait être bon pour les 3 références metrics_table ... (voir quels index sont actuellement disponibles, il y a une chance que vous ne pouvez pas besoin de créer un nouvel index)

CREATE NONCLUSTERED INDEX ixf_metricstable_errorcode_starttime ON dbo.metrics_table (
    error_code, 
    start_time 
    ) 
INCLUDE (
    end_time, 
    box, 
    process 
    ) 
WHERE 
    error_code = 0; 
0

je Between et a obtenu de bonnes performances en mon banc d'essai simple. J'ai également utilisé columnstore comme 50 millions d'enregistrements est volumes DW:

CREATE TABLE dbo.metrics_table (
    rowId  INT IDENTITY, 
    start_time DATETIME NOT NULL, 
    end_time DATETIME NOT NULL, 
    box   VARCHAR(10) NOT NULL, 
    process  VARCHAR(10) NOT NULL, 
    error_code INT NOT NULL 
); 


-- Add records 
;WITH cte AS (
SELECT TOP 3334 ROW_NUMBER() OVER (ORDER BY (SELECT 1)) rn 
FROM sys.columns c1 
    CROSS JOIN sys.columns c2 
    CROSS JOIN sys.columns c3 
) 
INSERT INTO dbo.metrics_table (start_time, end_time, box, process, error_code) 
SELECT 
    DATEADD(ms, rn, DATEADD(day, rn % 365, '1 Jan 2017')) AS start_time, 
    DATEADD(ms, rn % 409, DATEADD(ms, rn, DATEADD(day, rn % 365, '1 Jan 2017'))) AS end_time, 
    'box' + CAST(boxes.box AS VARCHAR(10)) box, 
    'process' + CAST(boxes.box AS VARCHAR(10)) process, 
    ABS(CAST(rn % 3000 AS BIT) -1) error_code 
FROM cte c 
    CROSS JOIN (SELECT TOP 10 rn FROM cte) AS boxes(box) 
    CROSS JOIN (SELECT TOP 30 rn FROM cte) AS processes(process); 


-- Create normal clustered index to order the data 
CREATE CLUSTERED INDEX cci_metrics_table ON dbo.metrics_table (start_time, end_time, box, process); 
--CREATE CLUSTERED INDEX cci_metrics_table ON dbo.metrics_table (box, process, start_time, end_time); 

-- Convert to columnstore 
CREATE CLUSTERED COLUMNSTORE INDEX cci_metrics_table ON dbo.metrics_table WITH (MAXDOP = 1, DROP_EXISTING = ON); 



IF OBJECT_ID('tempdb..#tmp1') IS NOT NULL DROP TABLE #tmp1 

-- two hour window before, 1 hour window after 
SELECT 
    c1.start_time, 
    c1.end_time, 
    c1.box, 
    c1.process, 
    DATEDIFF(ms, c1.start_time, c1.end_time) AS duration_ms, 
    DATEPART(dw, c1.start_time) AS day_of_week, 
    DATEPART(hour, c1.start_time) AS hour_of_day, 
    c2.xavg, 
    c2.num_process_total, 
    c2.num_process_unique, 
    c2.num_box_unique, 
    c3.percent_over_thresh 

INTO #tmp1 

FROM dbo.metrics_table c1 
    CROSS APPLY 
     (
     SELECT 
      COUNT(1) AS num_process_total, 
      AVG(CAST(DATEDIFF(ms, start_time, end_time) AS NUMERIC)) xavg, 
      COUNT(DISTINCT process) num_process_unique, 
      COUNT(DISTINCT box) num_box_unique 
     FROM dbo.metrics_table c2 
     WHERE c2.error_code = 0 
      AND c2.start_time Between DATEADD(minute, -120, c1.start_time) And c1.start_time 
      AND c1.start_time > c2.start_time 
     ) c2 

    CROSS APPLY 
     (
     SELECT 
      AVG(CASE WHEN DATEDIFF(ms, c4.start_time, c4.end_time) > 1000 THEN 1.0 ELSE 0.0 END) percent_over_thresh 
     FROM dbo.metrics_table c4 
     WHERE c4.error_code = 0 
      AND c4.start_time Between c1.start_time And DATEADD(minute, 60, c1.start_time) 
      AND c4.start_time > c1.start_time 
     ) c3 

WHERE error_code = 0