2013-12-16 14 views
7

Sto cercando alcuni consigli su come ottimizzare questa query.Aggiornamento di milioni di record sulla subquery interna interna - tecniche di ottimizzazione

Per ogni _piece_detail record che:

  1. contiene almeno un corrispondente _scan record (zip, zip_4, zip_delivery_point, numero_serie)
  2. appartiene a una società da mailing_groups (attraverso una catena di relazioni)
  3. è contrassegnato:
    1. first_scan_date_time che è maggiore della MIN(scan_date_time) del relativo _scan record
    2. latest_scan_date_time che è inferiore alla MAX(scan_date_time) dei i relativi _scan record

ho bisogno di:

  1. Impostare _piece_detail.first_scan_date_time-MIN(_scan.scan_date_time)
  2. Impostare _piece_detail.latest_scan_date_time-MAX(_scan.scan_date_time)

Poiché ho a che fare con milioni e milioni di record, sto cercando di ridurre il numero di record che devo effettivamente cercare. Qui ci sono alcuni fatti circa i dati:

  1. La tabella _piece_details è partizionato da job_id, così sembra rendere più senso per correre attraverso questi controlli in ordine di _piece_detail.job_id, _piece_detail.piece_id.
  2. La tabella record di scansione contiene oltre 100.000.000 di record in questo momento ed è suddiviso da (zip, zip_4, zip_delivery_point, serial_number, scan_date_time), che è la stessa chiave utilizzata per abbinare un _scan con un _piece_detail (a parte scan_date_time).
  3. Solo circa il 40% dei record _piece_detail appartiene a un mailing_group, ma non sappiamo quali sono questi finché non eseguiamo attraverso la relazione completa di join.
  4. Solo il 30% circa dei record _scan appartiene a un _piece_detail con un mailing_group.
  5. Ci sono in genere tra 0 e 4 _scan record per _piece_detail.

Ora, sto avendo un sacco di tempo per trovare un modo per eseguire questo in modo decente. Avevo inizialmente iniziare con qualcosa di simile:

UPDATE _piece_detail 
    INNER JOIN (
     SELECT _piece_detail.job_id, _piece_detail.piece_id, MIN(_scan.scan_date_time) as first_scan_date_time, MAX(_scan.scan_date_time) as latest_scan_date_time 
     FROM _piece_detail 
      INNER JOIN _container_quantity 
       ON _piece_detail.cqt_database_id = _container_quantity.cqt_database_id 
       AND _piece_detail.job_id = _container_quantity.job_id 
      INNER JOIN _container_summary 
       ON _container_quantity.container_id = _container_summary.container_id 
       AND _container_summary.job_id = _container_quantity.job_id 
      INNER JOIN _mail_piece_unit 
       ON _container_quantity.mpu_id = _mail_piece_unit.mpu_id 
       AND _container_quantity.job_id = _mail_piece_unit.job_id 
      INNER JOIN _header 
       ON _header.job_id = _piece_detail.job_id 
      INNER JOIN mailing_groups 
       ON _mail_piece_unit.mpu_company = mailing_groups.mpu_company 
      INNER JOIN _scan 
       ON _scan.zip = _piece_detail.zip 
       AND _scan.zip_4 = _piece_detail.zip_4 
       AND _scan.zip_delivery_point = _piece_detail.zip_delivery_point 
       AND _scan.serial_number = _piece_detail.serial_number 
     GROUP BY _piece_detail.job_id, _piece_detail.piece_id, _scan.zip, _scan.zip_4, _scan.zip_delivery_point, _scan.serial_number 
    ) as t1 ON _piece_detail.job_id = t1.job_id AND _piece_detail.piece_id = t1.piece_id 
SET _piece_detail.first_scan_date_time = t1.first_scan_date_time, _piece_detail.latest_scan_date_time = t1.latest_scan_date_time 
WHERE _piece_detail.first_scan_date_time < t1.first_scan_date_time 
    OR _piece_detail.latest_scan_date_time > t1.latest_scan_date_time; 

ho pensato che questo potrebbe aver tentato di caricare troppo in memoria in una sola volta e potrebbe non essere utilizzando gli indici in modo corretto.

poi ho pensato che potrei essere in grado di evitare di fare quell'enorme subquery unito e aggiungere due sottoquery leftjoin per ottenere il min/max in questo modo:

UPDATE _piece_detail 
    INNER JOIN _container_quantity 
     ON _piece_detail.cqt_database_id = _container_quantity.cqt_database_id 
     AND _piece_detail.job_id = _container_quantity.job_id 
    INNER JOIN _container_summary 
     ON _container_quantity.container_id = _container_summary.container_id 
     AND _container_summary.job_id = _container_quantity.job_id 
    INNER JOIN _mail_piece_unit 
     ON _container_quantity.mpu_id = _mail_piece_unit.mpu_id 
     AND _container_quantity.job_id = _mail_piece_unit.job_id 
    INNER JOIN _header 
     ON _header.job_id = _piece_detail.job_id 
    INNER JOIN mailing_groups 
     ON _mail_piece_unit.mpu_company = mailing_groups.mpu_company 
    LEFT JOIN _scan fs ON (fs.zip, fs.zip_4, fs.zip_delivery_point, fs.serial_number) = (
     SELECT zip, zip_4, zip_delivery_point, serial_number 
     FROM _scan 
     WHERE zip = _piece_detail.zip 
      AND zip_4 = _piece_detail.zip_4 
      AND zip_delivery_point = _piece_detail.zip_delivery_point 
      AND serial_number = _piece_detail.serial_number 
     ORDER BY scan_date_time ASC 
     LIMIT 1 
     ) 
    LEFT JOIN _scan ls ON (ls.zip, ls.zip_4, ls.zip_delivery_point, ls.serial_number) = (
     SELECT zip, zip_4, zip_delivery_point, serial_number 
     FROM _scan 
     WHERE zip = _piece_detail.zip 
      AND zip_4 = _piece_detail.zip_4 
      AND zip_delivery_point = _piece_detail.zip_delivery_point 
      AND serial_number = _piece_detail.serial_number 
     ORDER BY scan_date_time DESC 
     LIMIT 1 
     ) 
SET _piece_detail.first_scan_date_time = fs.scan_date_time, _piece_detail.latest_scan_date_time = ls.scan_date_time 
WHERE _piece_detail.first_scan_date_time < fs.scan_date_time 
    OR _piece_detail.latest_scan_date_time > ls.scan_date_time 

Questi sono i spiega quando li converto a SELEZIONARE dichiarazioni:

+----+-------------+---------------------+--------+----------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+--------+----------------------------------------------+ 
| id | select_type | table    | type | possible_keys          | key   | key_len | ref                             | rows | Extra          | 
+----+-------------+---------------------+--------+----------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+--------+----------------------------------------------+ 
| 1 | PRIMARY  | <derived2>   | ALL | NULL            | NULL   | NULL | NULL                             | 844161 | NULL           | 
| 1 | PRIMARY  | _piece_detail  | eq_ref | PRIMARY,first_scan_date_time,latest_scan_date_time | PRIMARY  | 18  | t1.job_id,t1.piece_id                         |  1 | Using where         | 
| 2 | DERIVED  | _header    | index | PRIMARY           | date_prepared | 3  | NULL                             |  87 | Using index; Using temporary; Using filesort | 
| 2 | DERIVED  | _piece_detail  | ref | PRIMARY,cqt_database_id,zip      | PRIMARY  | 10  | odms._header.job_id                         | 9703 | NULL           | 
| 2 | DERIVED  | _container_quantity | eq_ref | unique,mpu_id,job_id,job_id_container_quantity  | unique  | 14  | odms._header.job_id,odms._piece_detail.cqt_database_id                 |  1 | NULL           | 
| 2 | DERIVED  | _mail_piece_unit | eq_ref | PRIMARY,company,job_id_mail_piece_unit    | PRIMARY  | 14  | odms._container_quantity.mpu_id,odms._header.job_id                 |  1 | Using where         | 
| 2 | DERIVED  | mailing_groups  | eq_ref | PRIMARY           | PRIMARY  | 27  | odms._mail_piece_unit.mpu_company                      |  1 | Using index         | 
| 2 | DERIVED  | _container_summary | eq_ref | unique,container_id,job_id_container_summary  | unique  | 14  | odms._header.job_id,odms._container_quantity.container_id                |  1 | Using index         | 
| 2 | DERIVED  | _scan    | ref | PRIMARY           | PRIMARY  | 28  | odms._piece_detail.zip,odms._piece_detail.zip_4,odms._piece_detail.zip_delivery_point,odms._piece_detail.serial_number |  1 | Using index         | 
+----+-------------+---------------------+--------+----------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+--------+----------------------------------------------+ 

+----+--------------------+---------------------+--------+--------------------------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+-----------+-----------------------------------------------------------------+ 
| id | select_type  | table    | type | possible_keys              | key   | key_len | ref                             | rows  | Extra               | 
+----+--------------------+---------------------+--------+--------------------------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+-----------+-----------------------------------------------------------------+ 
| 1 | PRIMARY   | _header    | index | PRIMARY               | date_prepared | 3  | NULL                             |  87 | Using index              | 
| 1 | PRIMARY   | _piece_detail  | ref | PRIMARY,cqt_database_id,first_scan_date_time,latest_scan_date_time | PRIMARY  | 10  | odms._header.job_id                         |  9703 | NULL               | 
| 1 | PRIMARY   | _container_quantity | eq_ref | unique,mpu_id,job_id,job_id_container_quantity      | unique  | 14  | odms._header.job_id,odms._piece_detail.cqt_database_id                 |   1 | NULL               | 
| 1 | PRIMARY   | _mail_piece_unit | eq_ref | PRIMARY,company,job_id_mail_piece_unit        | PRIMARY  | 14  | odms._container_quantity.mpu_id,odms._header.job_id                 |   1 | Using where              | 
| 1 | PRIMARY   | mailing_groups  | eq_ref | PRIMARY               | PRIMARY  | 27  | odms._mail_piece_unit.mpu_company                      |   1 | Using index              | 
| 1 | PRIMARY   | _container_summary | eq_ref | unique,container_id,job_id_container_summary      | unique  | 14  | odms._header.job_id,odms._container_quantity.container_id                |   1 | Using index              | 
| 1 | PRIMARY   | fs     | index | NULL                | updated  | 1  | NULL                             | 102462928 | Using where; Using index; Using join buffer (Block Nested Loop) | 
| 1 | PRIMARY   | ls     | index | NULL                | updated  | 1  | NULL                             | 102462928 | Using where; Using index; Using join buffer (Block Nested Loop) | 
| 3 | DEPENDENT SUBQUERY | _scan    | ref | PRIMARY               | PRIMARY  | 28  | odms._piece_detail.zip,odms._piece_detail.zip_4,odms._piece_detail.zip_delivery_point,odms._piece_detail.serial_number |   1 | Using where; Using index; Using filesort      | 
| 2 | DEPENDENT SUBQUERY | _scan    | ref | PRIMARY               | PRIMARY  | 28  | odms._piece_detail.zip,odms._piece_detail.zip_4,odms._piece_detail.zip_delivery_point,odms._piece_detail.serial_number |   1 | Using where; Using index; Using filesort      | 
+----+--------------------+---------------------+--------+--------------------------------------------------------------------+---------------+---------+------------------------------------------------------------------------------------------------------------------------+-----------+-----------------------------------------------------------------+ 

Ora, guardando la spiega generato da ogni, davvero non si può dire che mi sta dando la migliore bang per il mio buck. Il primo mostra meno righe totali quando si moltiplica la colonna delle righe, ma il secondo sembra eseguire un po 'più veloce.

C'è qualcosa che potrei fare per ottenere gli stessi risultati aumentando le prestazioni attraverso la modifica della struttura della query?

+0

Suggerisco di non farlo affatto. Stai violando la normalizzazione tentando di memorizzare valori calcolati. Basta selezionarlo quando ne hai bisogno. –

+2

Ho scoperto che ci sono situazioni in cui ha senso deviare dalle regole di normalizzazione - in un mondo perfetto avrei una macchina da un milione di dollari in grado di scricchiolare questi valori al volo per ogni richiesta, ma a tale riguardo sono limitato . C'è una grande quantità di aggregazione fatta per i rapporti e questi valori sono usati spesso. Il vantaggio in termini di prestazioni di averli disponibili all'interno della stessa riga del pezzo è significativo. –

risposta

0

Perché non utilizzi sottocliche per ogni join? Compresi i join interni?

INNER JOIN (SELECT field1, field2, field 3 from _container_quantity order by 1,2,3) 
    ON _piece_detail.cqt_database_id = _container_quantity.cqt_database_id 
    AND _piece_detail.job_id = _container_quantity.job_id 
INNER JOIN (SELECT field1, field2, field3 from _container_summary order by 1,2,3) 
    ON _container_quantity.container_id = _container_summary.container_id 
    AND _container_summary.job_id = _container_quantity.job_id 

Stai sicuramente tirando un sacco in memoria non limitando la tua selezione su quelle interazioni interne. Utilizzando l'ordine di 1,2,3 alla fine di ogni sottoquery, si crea un indice per ogni sottoquery. Il tuo unico indice è sulle intestazioni e non ti unisci a _headers ....

Un paio di suggerimenti per ottimizzare questa query. O creare gli indici necessari in ogni tabella o utilizzare le clausole Sub-query join per creare manualmente gli indici necessari al volo.

Ricorda inoltre che quando esegui un join a sinistra su una tabella "temporanea" piena di aggregati, stai solo chiedendo dei problemi di prestazioni.

contiene almeno un corrispondente _scan record (zip, zip_4, zip_delivery_point, num_seriale)

Umm ... questo è il vostro primo punto in quello che si vuole fare, ma nessuno di questi i campi sono indicizzati?

+0

In realtà, tutti questi campi sono indicizzati: costituiscono la mia chiave primaria composita in questo ordine, che dovrebbe posizionare fisicamente tutte le scansioni per lo stesso pezzo direttamente l'una accanto all'altra. E il motivo per cui non sto usando sottoquery per i miei join interiori come nel tuo primo esempio è perché ogni _piece_detail ha una relazione univoca con tutte quelle tabelle (che poi raggruppo per determinati campi e poi conto gli uniques). Non capisco come le sottoquery su qualcosa di diverso dai risultati _scan migliorerebbero le prestazioni. Comunque giocherò con i tuoi suggerimenti. Grazie. –

+1

Questo ha più senso. Apprezzo la tua risposta. Stavo suggerendo che le sottoquery aumenterebbero le prestazioni perché i tuoi indici non sono realmente menzionati. Sebbene tu mostri una conoscenza generale estesa in MySQL, non sei sicuro di aver gettato questa idea in giro. – Hituptony

0

Dai tuoi spieghi risultati sembra che la subquery stia attraversando tutte le righe due volte allora, che ne dici di mantenere MIN/MAX dal primo e utilizzare solo un join sinistro anziché due?

1

Disabilita aggiornamento dell'indice mentre si fanno gli aggiornamenti di massa

ALTER TABLE _piece_detail DISABLE KEYS; 

UPDATE ....; 

ALTER TABLE _piece_detail ENABLE KEYS; 

Fare riferimento ai documenti di MySQL: http://dev.mysql.com/doc/refman/5.0/en/alter-table.html

EDIT: Dopo aver esaminato la documentazione MySQL indicai, vedo la documentazione specifica questo per la tabella MyISAM, ed è nit nitido per altri tipi di tabella. Ulteriori soluzioni qui: How to disable index in innodb

1

C'è qualcosa che mi è stato insegnato e che seguo rigorosamente fino ad oggi - Crea tante tabelle temporanee che desideri evitando l'uso di tabelle derivate.Soprattutto in caso di UPDATE/DELETE/INSERT come

  1. non puoi prevedere l'indice sulle tabelle derivate
  2. Le tabelle derivate potrebbero non essere tenute in memoria se il gruppo di risultati è grande
  3. La tabella (MyISAM)/rows (Innodb) può essere bloccato per un tempo più lungo ogni volta che viene eseguita la query derivata. Preferisco una tabella temporanea che abbia una chiave primaria unita alla tabella padre.

E soprattutto rende il codice un aspetto ordinato e leggibile.

Il mio approccio sarà

CREATE table temp xxx(...) 
INSERT INTO xxx select q from y inner join z....; 
UPDATE _piece_detail INNER JOIN xxx on (...) SET ...; 

ridurre i tempi di inattività sempre voi !!

+0

non potrebbe essere più d'accordo, un processo di aggiornamento ben progettato attraverso una serie di tabelle temporanee vedrà diminuire il tempo di esecuzione. –

Problemi correlati