2015-05-21 10 views
5

Ho una richiesta SQL che prende il 100% della mia CPU VM mentre funziona. Voglio sapere come ottimizzare esso:Ottimizzazione richiesta SQL

SELECT g.name AS hostgroup 
    , h.name AS hostname 
    , a.host_id 
    , s.display_name AS servicename 
    , a.service_id 
    , a.entry_time AS ack_time 
    , ( SELECT ctime 
      FROM logs 
      WHERE logs.host_id = a.host_id 
      AND logs.service_id = a.service_id 
      AND logs.ctime < a.entry_time 
      AND logs.status IN (1, 2, 3) 
      AND logs.type = 1 
      ORDER BY logs.log_id DESC 
      LIMIT 1) AS start_time 
    , ar.acl_res_name AS timeperiod 
    , a.state AS state 
    , a.author 
    , a.acknowledgement_id AS ack_id 
FROM centstorage.acknowledgements a 
LEFT JOIN centstorage.hosts h ON a.host_id = h.host_id 
LEFT JOIN centstorage.services s ON a.service_id = s.service_id 
LEFT JOIN centstorage.hosts_hostgroups p ON a.host_id = p.host_id 
LEFT JOIN centstorage.hostgroups g ON g.hostgroup_id = p.hostgroup_id 
LEFT JOIN centreon.hostgroup_relation hg ON a.host_id = hg.host_host_id 
LEFT JOIN centreon.acl_resources_hg_relations hh ON hg.hostgroup_hg_id = hh.hg_hg_id 
LEFT JOIN centreon.acl_resources ar ON hh.acl_res_id = ar.acl_res_id 
WHERE ar.acl_res_name != 'All Resources' 
AND YEAR(FROM_UNIXTIME(a.entry_time)) = YEAR(CURDATE()) 
AND MONTH(FROM_UNIXTIME(a.entry_time)) = MONTH(CURDATE()) 
AND a.service_id is not null 
ORDER BY a.acknowledgement_id ASC 

Il problema è in questa parte:

(SELECT ctime FROM logs 
WHERE logs.host_id = a.host_id 
    AND logs.service_id = a.service_id 
    AND logs.ctime < a.entry_time 
    AND logs.status IN (1, 2, 3) 
    AND logs.type = 1 
ORDER BY logs.log_id DESC 
LIMIT 1) AS start_time 

I registri della tabella è davvero enorme e alcuni amici mi ha detto di utilizzare un buffer tavolo/base di dati ma ho abbastanza sapevo a queste cose e non so come farlo.

C'è un spiegare estesa del query: Here !

Sembra che sarà esaminato solo il 2 fila dei registri della tabella e allora perché ci vuole così tanto tempo? (C'è una riga 560000 nei registri della tabella).

Ecco tutti gli indici di tali tabelle:

centstorage.acknowledgements:

enter image description here centstorage.hosts:

enter image description here centstorage.services:

enter image description here centstorage.hosts_hostgroups:

enter image description here centstorage.hostgroups:

enter image description here centreon.hostgroup_relation:

enter image description here centreon.acl_resources_hg_relations:

enter image description here centreon.acl_resources:

enter image description here

+0

Quale prodotto dbms? Definizioni di tabelle e indici, ecc. – jarlh

+2

Prima di tutto controlla il piano di esecuzione e verifica se ti manca un indice. – Galma88

+0

Sto usando MySQL. –

risposta

0

Per SQL Server v'è la possibilità di definire il massimo grado di parallelismo della vostra query utilizzando MAXDOP

Ad esempio si può definire, al termine della query

option (maxdop 2) 

Sono abbastanza sicuro che c'è un equivalente in MySql.

Si può provare ad avvicinarsi a questa situazione se il tempo di esecuzione non è rilevante.

+1

non esiste una cosa del genere per mysql, non senza andare per i componenti aggiuntivi di terze parti –

0
  1. creare una tabella temporanea dalla quale condizione per riconoscimenti, lo schema avrà colonna richiesto nel risultato finale e utilizzato in JOIN con tutti i vostri 7 tavoli

    CREATE TEMPORARY TABLE __tempacknowledgements AS SELECT g.name AS hostgroup 
        , '' AS hostname 
        , a.host_id 
        , s.display_name AS servicename 
        , a.service_id 
        , a.entry_time AS ack_time 
        , '' AS AS start_time 
        , '' AS timeperiod 
        , a.state AS state 
        , a.author 
        , a.acknowledgement_id AS ack_id 
    FROM centstorage.acknowledgements a 
    WHERE YEAR(FROM_UNIXTIME(a.entry_time)) = YEAR(CURDATE()) 
    AND MONTH(FROM_UNIXTIME(a.entry_time)) = MONTH(CURDATE()) 
    AND a.service_id IS NOT NULL 
    ORDER BY a.acknowledgement_id ASC; 
    

oppure crearne uno usando una corretta definizione di colonna

  1. Aggiornare i campi da tutte le tabelle che hanno lasciato il join, è possibile utilizzare Inner Join in aggiornamento. Dovresti scrivere 7 diverse dichiarazioni di aggiornamento. 2 esempi sono riportati di seguito.

    UPDATE __tempacknowledgements a JOIN centstorage.hosts h USING(host_id) 
    SET a.name=h.name; 
    
    UPDATE __tempacknowledgements s JOIN centstorage.services h USING(service_id) 
    SET a.acl_res_name=s.acl_res_name; 
    
  2. modo simile aggiornamento ctime da tronchi mediante registrazione con Logs, questo è istruzione di aggiornamento 8 °.

  3. selezionare la selezione dalla tabella temporanea.
  4. goccia tabella temporanea

una sp può essere scritto per questo.

+0

Sembra essere un buon modo per aiutarmi ma, sono ancora un principiante nelle query complesse SQL .. . Quindi non capisco tutto –

+0

Aggiunto qualche esempio di query, può aiutarti a ottenere l'idea della soluzione proposta. – Anil

0

Trasformare LEFT JOIN in JOIN se non si ha realmente bisogno di LEFT.

AND YEAR(FROM_UNIXTIME(a.entry_time)) = YEAR(CURDATE()) 
AND MONTH(FROM_UNIXTIME(a.entry_time)) = MONTH(CURDATE()) 
AND a.service_id is not null 

Avete righe con a.service_id is not null? Altrimenti, sbarazzatene.

Come già accennato, il confronto di data non ottimizza. Ecco cosa usare invece:

AND a.entry_time >= CONCAT(LEFT(CURDATE(), 7), '-01') 
AND a.entry_time < CONCAT(LEFT(CURDATE(), 7), '-01') + INTERVAL 1 MONTH 

E aggiungere uno di questi (a seconda del mio commento sopra):

INDEX(entry_time) 
INDEX(service_id, entry_time) 

Il subquery correlata è difficile da ottimizzare. Tale indice (su logs) può aiutare:

INDEX(type, host_id, service_id, status) 
0

DOVE IN è il tempo assassino! Invece di logs.status IN (1, 2, 3) uso logs.status = 1 o 2 o logs.status = logs.status = 3

+0

Potresti elaborare un po '? –

0

ho LEGGERMENTE riformattato query per il mio riferimento di leggibilità e vedere meglio le relazioni tra i tavoli ... altrimenti ignori quella parte.

SELECT 
     g.name AS hostgroup, 
     h.name AS hostname, 
     a.host_id, 
     s.display_name AS servicename, 
     a.service_id, 
     a.entry_time AS ack_time, 
     (SELECT 
       ctime 
      FROM 
       logs 
      WHERE 
        logs.host_id = a.host_id 
       AND logs.service_id = a.service_id 
       AND logs.ctime < a.entry_time 
       AND logs.status IN (1, 2, 3) 
       AND logs.type = 1 
      ORDER BY 
       logs.log_id DESC 
      LIMIT 1) AS start_time, 
     ar.acl_res_name AS timeperiod, 
     a.state AS state, 
     a.author, 
     a.acknowledgement_id AS ack_id 
    FROM 
     centstorage.acknowledgements a 
     LEFT JOIN centstorage.hosts h 
      ON a.host_id = h.host_id 
     LEFT JOIN centstorage.services s 
      ON a.service_id = s.service_id 
     LEFT JOIN centstorage.hosts_hostgroups p 
      ON a.host_id = p.host_id 
      LEFT JOIN centstorage.hostgroups g 
       ON p.hostgroup_id = g.hostgroup_id 
     LEFT JOIN centreon.hostgroup_relation hg 
      ON a.host_id = hg.host_host_id 
      LEFT JOIN centreon.acl_resources_hg_relations hh 
       ON hg.hostgroup_hg_id = hh.hg_hg_id 
       LEFT JOIN centreon.acl_resources ar 
        ON hh.acl_res_id = ar.acl_res_id 
    WHERE 
      ar.acl_res_name != 'All Resources' 
     AND YEAR(FROM_UNIXTIME(a.entry_time)) = YEAR(CURDATE()) 
     AND MONTH(FROM_UNIXTIME(a.entry_time)) = MONTH(CURDATE()) 
     AND a.service_id is not null 
    ORDER BY 
     a.acknowledgement_id ASC 

Desidero in primo consiglio di iniziare con il vostro tavolo "riconoscimenti" e hanno un indice a un minimo di (entry_time, acknowledgement_id). Successivamente, aggiorna la clausola WHERE. Poiché stai eseguendo una funzione per convertire il timestamp unix in una data e acquisendo rispettivamente l'ANNO (e il mese), non credo che stia utilizzando l'indice in quanto deve calcolarlo per ogni riga. Per elevare ciò, un timestamp unix non è altro che un numero che rappresenta secondi da un punto specifico nel tempo. Se stai cercando un mese specifico, calcola l'inizio e la fine di unix e corri per quell'intervallo. Qualcosa di simile a...

e a.entry_time> = UNIX_TIMESTAMP ('2015/10/01') e a.entry_time < UNIX_TIMESTAMP ('2015/11/01')

In questo modo, esso rappresenta per tutti i secondi all'interno del mese fino alle 11:59:59 del 31 ottobre, poco prima del 1 ° novembre.

Poi, senza i miei occhiali per vedere tutte le immagini più chiaramente, e breve tempo di questa mattina, avrei assicurarsi di avere almeno i seguenti indici su ogni tavolo, rispettivamente

table    index 
logs    (host_id, service_id, type, status, ctime, log_id) 
acknowledgements (entry_time, acknowledgement_id, host_id, service_id) 
hosts    (host_id, name) 
services   (service_id, display_name) 
hosts_hostgroups (host_id, hostgroup_id) 
hostgroups   (hostgroup_id, name) 
hostgroup_relation (host_host_id, hostgroup_hg_id) 
acl_resources_hg_relations (hh_hg_id, acl_res_id) 
acl_resources ar (acl_res_id, acl_res_name) 

Infine, il sub-correlato il campo di query sarà un killer mentre viene elaborato per ogni riga, ma si spera che le altre idee di ottimizzazione dell'indice possano migliorare le prestazioni.